[CalendarServer-changes] [10937] CalendarServer/branches/users/gaya/directorybacker

source_changes at macosforge.org source_changes at macosforge.org
Fri Mar 15 14:47:42 PDT 2013


Revision: 10937
          http://trac.calendarserver.org//changeset/10937
Author:   gaya at apple.com
Date:     2013-03-15 14:47:42 -0700 (Fri, 15 Mar 2013)
Log Message:
-----------
merge from trunk

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/amppush.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/applepush.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/notifier.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_amppush.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_applepush.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_notifier.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/config.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/gateway.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/purge.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/push.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/test_gateway.py
    CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/upgrade.py
    CalendarServer/branches/users/gaya/directorybacker/conf/auth/resources-test.xml
    CalendarServer/branches/users/gaya/directorybacker/conf/caldavd-apple.plist
    CalendarServer/branches/users/gaya/directorybacker/run
    CalendarServer/branches/users/gaya/directorybacker/setup.py
    CalendarServer/branches/users/gaya/directorybacker/support/Makefile.Apple
    CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/queue.py
    CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/test/test_queue.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/config.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts-modified.xml
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts.xml
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/augments.xml
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/proxies.xml
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_directory.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_proxyprincipalmembers.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/scheduling/imip/inbound.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/test/test_config.py
    CalendarServer/branches/users/gaya/directorybacker/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_12_to_13.sql
    CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_13_to_14.sql

Removed Paths:
-------------
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/__init__.py
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarcommonextra.py
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendardemotion.py
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarmigrator.py
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarpromotion.py
    CalendarServer/branches/users/gaya/directorybacker/contrib/migration/test/
    CalendarServer/branches/users/gaya/directorybacker/doc/RFC/draft-daboo-srv-caldav.txt

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/amppush.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/amppush.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/amppush.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -71,7 +71,7 @@
         controlSocket.addFactory(PUSH_ROUTE, AMPPushForwardingFactory(self))
 
     @inlineCallbacks
-    def enqueue(self, id, dataChangedTimestamp=None):
+    def enqueue(self, transaction, id, dataChangedTimestamp=None):
         if dataChangedTimestamp is None:
             dataChangedTimestamp = int(time.time())
         for protocol in self.protocols:
@@ -92,7 +92,7 @@
     def enqueueFromWorker(self, id, dataChangedTimestamp=None):
         if dataChangedTimestamp is None:
             dataChangedTimestamp = int(time.time())
-        self.master.enqueue(id, dataChangedTimestamp=dataChangedTimestamp)
+        self.master.enqueue(None, id, dataChangedTimestamp=dataChangedTimestamp)
         return {"status" : "OK"}
  
 
@@ -145,7 +145,7 @@
         self.log_debug("Removed subscriber")
         self.subscribers.remove(p)
 
-    def enqueue(self, pushKey, dataChangedTimestamp=None):
+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
         """
         Sends an AMP push notification to any clients subscribing to this pushKey.
 

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/applepush.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/applepush.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -176,7 +176,7 @@
 
 
     @inlineCallbacks
-    def enqueue(self, pushKey, dataChangedTimestamp=None):
+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
         """
         Sends an Apple Push Notification to any device token subscribed to
         this pushKey.
@@ -207,9 +207,7 @@
         if provider is not None:
 
             # Look up subscriptions for this key
-            txn = self.store.newTransaction()
-            subscriptions = (yield txn.apnSubscriptionsByKey(pushKey))
-            yield txn.commit()
+            subscriptions = (yield transaction.apnSubscriptionsByKey(pushKey))
 
             numSubscriptions = len(subscriptions)
             if numSubscriptions > 0:

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/notifier.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/notifier.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/notifier.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -32,7 +32,7 @@
 
 class PushNotificationWork(WorkItem, fromTable(schema.PUSH_NOTIFICATION_WORK)):
 
-    group = "PUSH_ID"
+    group = property(lambda self: self.pushID)
 
     @inlineCallbacks
     def doWork(self):
@@ -44,7 +44,7 @@
 
         pushDistributor = self.transaction._pushDistributor
         if pushDistributor is not None:
-            yield pushDistributor.enqueue(self.pushID)
+            yield pushDistributor.enqueue(self.transaction, self.pushID)
 
 
 
@@ -204,12 +204,15 @@
         self.observers = observers 
 
     @inlineCallbacks
-    def enqueue(self, pushKey):
+    def enqueue(self, transaction, pushKey):
         """
         Pass along enqueued pushKey to any observers
 
+        @param transaction: a transaction to use, if needed
+        @type transaction: L{CommonStoreTransaction}
+
         @param pushKey: the push key to distribute to the observers
         @type pushKey: C{str}
         """
         for observer in self.observers:
-            yield observer.enqueue(pushKey)
+            yield observer.enqueue(transaction, pushKey)

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_amppush.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_amppush.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_amppush.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -57,7 +57,7 @@
         self.assertTrue(client3.subscribedToID("/CalDAV/localhost/user03/"))
 
         dataChangedTimestamp = 1354815999
-        service.enqueue("/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
+        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
         self.assertEquals(len(client1.history), 0)
         self.assertEquals(len(client2.history), 0)
         self.assertEquals(len(client3.history), 0)
@@ -74,7 +74,7 @@
         client1.reset()
         client2.reset()
         client2.unsubscribe("token2", "/CalDAV/localhost/user01/")
-        service.enqueue("/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
+        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
         self.assertEquals(len(client1.history), 0)
         clock.advance(1)
         self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
@@ -87,7 +87,7 @@
         client1.reset()
         client2.reset()
         client2.subscribe("token2", "/CalDAV/localhost/user01/")
-        service.enqueue("/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
+        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
         self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
         self.assertEquals(client2.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
 

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_applepush.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_applepush.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_applepush.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -125,8 +125,10 @@
 
         # Notification arrives from calendar server
         dataChangedTimestamp = 1354815999
-        yield service.enqueue("/CalDAV/calendars.example.com/user01/calendar/",
+        txn = self.store.newTransaction()
+        yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/",
             dataChangedTimestamp=dataChangedTimestamp)
+        yield txn.commit()
 
         # The notifications should be in the queue
         self.assertTrue(((token, key1), dataChangedTimestamp) in service.providers["CalDAV"].queue)
@@ -165,7 +167,9 @@
         # Reset sent data
         providerConnector.transport.data = None
         # Send notification while service is connected
-        yield service.enqueue("/CalDAV/calendars.example.com/user01/calendar/")
+        txn = self.store.newTransaction()
+        yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/")
+        yield txn.commit()
         clock.advance(1) # so that first push is sent
         self.assertEquals(len(providerConnector.transport.data), 183)
         # Reset sent data

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_notifier.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_notifier.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/push/test/test_notifier.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -30,7 +30,7 @@
     def reset(self):
         self.history = []
 
-    def enqueue(self, id):
+    def enqueue(self, transaction, id):
         self.history.append(id)
         return(succeed(None))
 
@@ -40,7 +40,7 @@
     def test_enqueue(self):
         stub = StubService()
         dist = PushDistributor([stub])
-        yield dist.enqueue("testing")
+        yield dist.enqueue(None, "testing")
         self.assertEquals(stub.history, ["testing"])
 
     def test_getPubSubAPSConfiguration(self):
@@ -82,7 +82,7 @@
     def reset(self):
         self.history = []
 
-    def enqueue(self, pushID):
+    def enqueue(self, transaction, pushID):
         self.history.append(pushID)
 
 class PushNotificationWorkTests(TestCase):
@@ -117,6 +117,13 @@
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
         ))
+        # Enqueue a different pushID to ensure those are not grouped with
+        # the others:
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID="/CalDAV/localhost/baz/",
+        ))
+
         yield txn.commit()
         yield wp.whenExecuted()
-        self.assertEquals(pushDistributor.history, ["/CalDAV/localhost/bar/"])
+        self.assertEquals(pushDistributor.history,
+            ["/CalDAV/localhost/bar/", "/CalDAV/localhost/baz/"])

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/config.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/config.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/config.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -18,18 +18,56 @@
 from __future__ import print_function
 
 """
-This tool reads the Calendar Server configuration file and emits the
-requested value.
+This tool gets and sets Calendar Server configuration keys
 """
 
-import os, sys
 from getopt import getopt, GetoptError
+import os
+import plistlib
+import signal
+import sys
+import xml
 
-from twistedcaldav.config import ConfigurationError
+from twext.python.plistlib import readPlistFromString, writePlistToString
+from twistedcaldav.config import config, ConfigDict, ConfigurationError, mergeData
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+WRITABLE_CONFIG_KEYS = [
+    "EnableSSL",
+    "RedirectHTTPToHTTPS",
+    "EnableCalDAV",
+    "EnableCardDAV",
+    "DataRoot",
+    "SSLCertificate",
+    "SSLPrivateKey",
+    "SSLAuthorityChain",
+    "EnableSearchAddressBook",
+    "Authentication.Basic.Enabled",
+    "Authentication.Basic.AllowedOverWireUnencrypted",
+    "Authentication.Digest.Enabled",
+    "Authentication.Digest.AllowedOverWireUnencrypted",
+    "Authentication.Kerberos.Enabled",
+    "Authentication.Kerberos.AllowedOverWireUnencrypted",
+    "Authentication.Wiki.Enabled",
+    "Scheduling.iMIP.Enabled",
+    "Scheduling.iMIP.Receiving.Username",
+    "Scheduling.iMIP.Receiving.Server",
+    "Scheduling.iMIP.Receiving.Port",
+    "Scheduling.iMIP.Receiving.Type",
+    "Scheduling.iMIP.Receiving.UseSSL",
+    "Scheduling.iMIP.Sending.Username",
+    "Scheduling.iMIP.Sending.Server",
+    "Scheduling.iMIP.Sending.Port",
+    "Scheduling.iMIP.Sending.UseSSL",
+    "Scheduling.iMIP.Sending.Address",
+    "Notifications.Services.APNS.Enabled",
+    "Notifications.Services.APNS.CalDAV.CertificatePath",
+    "Notifications.Services.APNS.CalDAV.AuthorityChainPath",
+    "Notifications.Services.APNS.CalDAV.PrivateKeyPath",
+    "Notifications.Services.APNS.CardDAV.CertificatePath",
+    "Notifications.Services.APNS.CardDAV.AuthorityChainPath",
+    "Notifications.Services.APNS.CardDAV.PrivateKeyPath",
+]
 
-from calendarserver.tools.util import loadConfig
-
 def usage(e=None):
     if e:
         print(e)
@@ -42,6 +80,7 @@
     print("options:")
     print("  -h --help: print this help and exit")
     print("  -f --config: Specify caldavd.plist configuration path")
+    print("  -w --writeconfig: Specify caldavd.plist configuration path for writing")
 
     if e:
         sys.exit(64)
@@ -51,15 +90,17 @@
 def main():
     try:
         (optargs, args) = getopt(
-            sys.argv[1:], "hf:", [
+            sys.argv[1:], "hf:w:", [
                 "help",
                 "config=",
+                "writeconfig=",
             ],
         )
     except GetoptError, e:
         usage(e)
 
     configFileName = DEFAULT_CONFIG_FILE
+    writeConfigFileName = ""
 
     for opt, arg in optargs:
         if opt in ("-h", "--help"):
@@ -68,18 +109,332 @@
         elif opt in ("-f", "--config"):
             configFileName = arg
 
+        elif opt in ("-w", "--writeconfig"):
+            writeConfigFileName = arg
+
     try:
-        config = loadConfig(configFileName)
+        config.load(configFileName)
     except ConfigurationError, e:
         sys.stdout.write("%s\n" % (e,))
         sys.exit(1)
 
-    for configKey in args:
-        c = config
-        for subKey in configKey.split("."):
-            c = c.get(subKey, None)
-            if c is None:
-                sys.stderr.write("No such config key: %s\n" % configKey)
-                break
+    if not writeConfigFileName:
+        # If --writeconfig was not passed, use WritableConfigFile from
+        # main plist.  If that's an empty string, writes will happen to
+        # the main file.
+        writeConfigFileName = config.WritableConfigFile
+
+    if not writeConfigFileName:
+        writeConfigFileName = configFileName
+
+    writable = WritableConfig(config, writeConfigFileName)
+    writable.read()
+
+    if args:
+        for configKey in args:
+
+            if "=" in configKey:
+                # This is an assignment
+                configKey, stringValue = configKey.split("=")
+                value = writable.convertToValue(stringValue)
+                writable.set({configKey:value})
+            else:
+                # This is a read
+                c = config
+                for subKey in configKey.split("."):
+                    c = c.get(subKey, None)
+                    if c is None:
+                        sys.stderr.write("No such config key: %s\n" % configKey)
+                        break
+                sys.stdout.write("%s=%s\n" % (configKey, c))
+
+        writable.save(restart=True)
+
+    else:
+        # Read plist commands from stdin
+        rawInput = sys.stdin.read()
+        try:
+            plist = readPlistFromString(rawInput)
+        except xml.parsers.expat.ExpatError, e:
+            respondWithError(str(e))
+            return
+
+        # If the plist is an array, each element of the array is a separate
+        # command dictionary.
+        if isinstance(plist, list):
+            commands = plist
         else:
-            sys.stdout.write("%s\n" % c)
+            commands = [plist]
+
+        runner = Runner(commands)
+        runner.run()
+
+
+
+class Runner(object):
+
+    """
+    A class which carries out commands, which are plist strings containing
+    dictionaries with a "command" key, plus command-specific data.
+    """
+
+    def __init__(self, commands):
+        """
+        @param commands: the commands to run
+        @type commands: list of plist strings
+        """
+        self.commands = commands
+
+    def validate(self):
+        """
+        Validate all the commands by making sure this class implements
+        all the command keys.
+        @return: True if all commands are valid, False otherwise
+        """
+        # Make sure commands are valid
+        for command in self.commands:
+            if 'command' not in command:
+                respondWithError("'command' missing from plist")
+                return False
+            commandName = command['command']
+            methodName = "command_%s" % (commandName,)
+            if not hasattr(self, methodName):
+                respondWithError("Unknown command '%s'" % (commandName,))
+                return False
+        return True
+
+    def run(self):
+        """
+        Find the appropriate method for each command and call them.
+        """
+        try:
+            for command in self.commands:
+                commandName = command['command']
+                methodName = "command_%s" % (commandName,)
+                if hasattr(self, methodName):
+                    getattr(self, methodName)(command)
+                else:
+                    respondWithError("Unknown command '%s'" % (commandName,))
+
+        except Exception, e:
+            respondWithError("Command failed: '%s'" % (str(e),))
+            raise
+
+    def command_readConfig(self, command):
+        """
+        Return current configuration
+
+        @param command: the dictionary parsed from the plist read from stdin
+        @type command: C{dict}
+        """
+        result = {}
+        for keyPath in WRITABLE_CONFIG_KEYS:
+            value = getKeyPath(config, keyPath)
+            if value is not None:
+                setKeyPath(result, keyPath, value)
+        respond(command, result)
+
+    def command_writeConfig(self, command):
+        """
+        Write config to secondary, writable plist
+
+        @param command: the dictionary parsed from the plist read from stdin
+        @type command: C{dict}
+        """
+        writable = WritableConfig(config, config.WritableConfigFile)
+        writable.read()
+        valuesToWrite = command.get("Values", {})
+        for keyPath, value in flattenDictionary(valuesToWrite):
+            if keyPath in WRITABLE_CONFIG_KEYS:
+                writable.set(setKeyPath(ConfigDict(), keyPath, value))
+        try:
+            writable.save(restart=False)
+        except Exception, e:
+            respond(command, {"error": str(e)})
+        else:
+            config.reload()
+            self.command_readConfig(command)
+
+
+def setKeyPath(parent, keyPath, value):
+    """
+    Allows the setting of arbitrary nested dictionary keys via a single
+    dot-separated string.  For example, setKeyPath(parent, "foo.bar.baz",
+    "xyzzy") would create any intermediate missing directories (or whatever
+    class parent is, such as ConfigDict) so that the following structure
+    results:  parent = { "foo" : { "bar" : { "baz" : "xyzzy } } }
+
+    @param parent: the object to modify
+    @type parent: any dict-like object
+    @param keyPath: a dot-delimited string specifying the path of keys to
+        traverse
+    @type keyPath: C{str}
+    @param value: the value to set
+    @type value: c{object}
+    @return: parent
+    """
+    original = parent
+    parts = keyPath.split(".")
+    for part in parts[:-1]:
+        child = parent.get(part, None)
+        if child is None:
+            parent[part] = child = parent.__class__()
+        parent = child
+    parent[parts[-1]] = value
+    return original
+
+def getKeyPath(parent, keyPath):
+    """
+    Allows the getting of arbitrary nested dictionary keys via a single
+    dot-separated string.  For example, getKeyPath(parent, "foo.bar.baz")
+    would fetch parent["foo"]["bar"]["baz"].  If any of the keys don't
+    exist, None is returned instead.
+
+    @param parent: the object to traverse
+    @type parent: any dict-like object
+    @param keyPath: a dot-delimited string specifying the path of keys to
+        traverse
+    @type keyPath: C{str}
+    @return: the value at keyPath
+    """
+    parts = keyPath.split(".")
+    for part in parts[:-1]:
+        child = parent.get(part, None)
+        if child is None:
+            return None
+        parent = child
+    return parent.get(parts[-1], None)
+
+def flattenDictionary(dictionary, current=""):
+    """
+    Returns a generator of (keyPath, value) tuples for the given dictionary,
+    where each keyPath is a dot-separated string representing the complete
+    path to a nested key.
+
+    @param dictionary: the dict object to traverse
+    @type dictionary: C{dict}
+    @param current: do not use; used internally for recursion
+    @type current: C{str}
+    @return: generator of (keyPath, value) tuples
+    """
+    for key, value in dictionary.iteritems():
+        if isinstance(value, dict):
+            for result in flattenDictionary(value, current + key + "."):
+                yield result
+        else:
+            yield (current + key, value)
+
+
+def restartService(pidFilename):
+    """
+    Given the path to a PID file, sends a HUP signal to the contained pid
+    in order to cause calendar server to restart.
+
+    @param pidFilename: an absolute path to a PID file
+    @type pidFilename: C{str}
+    """
+    if os.path.exists(pidFilename):
+        pidFile = open(pidFilename, "r")
+        pid = pidFile.read().strip()
+        pidFile.close()
+        try:
+            pid = int(pid)
+        except ValueError:
+            return
+        try:
+            os.kill(pid, signal.SIGHUP)
+        except OSError:
+            pass
+
+
+class WritableConfig(object):
+    """
+    A wrapper around a Config object which allows writing of values.  The idea
+    is a deployment could have a master plist which doesn't change, and have
+    it include a plist file which does.  This class facilitates writing to that
+    included plist.
+    """
+
+    def __init__(self, wrappedConfig, fileName):
+        """
+        @param wrappedConfig: the Config object to read from
+        @type wrappedConfig: C{Config}
+        @param fileName: the full path to the modifiable plist
+        @type fileName: C{str}
+        """
+        self.config = wrappedConfig
+        self.fileName = fileName
+        self.changes = None
+        self.currentConfigSubset = ConfigDict()
+        self.dirty = False
+
+    def set(self, data):
+        """
+        Merges data into a ConfigDict of changes intended to be saved to disk
+        when save( ) is called.
+
+        @param data: a dict containing new values
+        @type data: C{dict}
+        """
+        if not isinstance(data, ConfigDict):
+            data = ConfigDict(mapping=data)
+        mergeData(self.currentConfigSubset, data)
+        self.dirty = True
+
+    def read(self):
+        """
+        Reads in the data contained in the writable plist file.
+
+        @return: C{ConfigDict}
+        """
+        if os.path.exists(self.fileName):
+            self.currentConfigSubset = ConfigDict(mapping=plistlib.readPlist(self.fileName))
+        else:
+            self.currentConfigSubset = ConfigDict()
+
+    def toString(self):
+        return plistlib.writePlistToString(self.currentConfigSubset)
+
+    def save(self, restart=False):
+        """
+        Writes any outstanding changes to the writable plist file.  Optionally
+        restart calendar server.
+
+        @param restart: whether to restart the calendar server.
+        @type restart: C{bool}
+        """
+        if self.dirty:
+            plistlib.writePlist(self.currentConfigSubset, self.fileName)
+            self.dirty = False
+            if restart:
+                restartService(self.config.PIDFile)
+
+    @classmethod
+    def convertToValue(cls, string):
+        """
+        Inspect string and convert the value into an appropriate Python data type
+        TODO: change this to look at actual types definied within stdconfig
+        """
+        if "." in string:
+            try:
+                value = float(string)
+            except ValueError:
+                value = string
+        else:
+            try:
+                value = int(string)
+            except ValueError:
+                if string == "True":
+                    value = True
+                elif string == "False":
+                    value = False
+                else:
+                    value = string
+        return value
+
+
+def respond(command, result):
+    sys.stdout.write(writePlistToString({'command' : command['command'], 'result' : result}))
+
+def respondWithError(msg, status=1):
+    sys.stdout.write(writePlistToString({'error' : msg, }))

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/gateway.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/gateway.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -465,7 +465,6 @@
         respond(command, {'EventsRemoved' : eventCount, "RetainDays" : retainDays})
 
 
-
 @inlineCallbacks
 def respondWithProxies(directory, command, principal, proxyType):
     proxies = []
@@ -508,21 +507,13 @@
     respond(command, result)
 
 
-
 def respond(command, result):
     sys.stdout.write(writePlistToString({'command' : command['command'], 'result' : result}))
 
 
-
 def respondWithError(msg, status=1):
     sys.stdout.write(writePlistToString({'error' : msg, }))
-    """
-    try:
-        reactor.stop()
-    except RuntimeError:
-        pass
-    sys.exit(status)
-    """
 
+
 if __name__ == "__main__":
     main()

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/purge.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/purge.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -792,10 +792,14 @@
             # FIXME: probably want a more elegant way to accomplish this,
             # since it requires the aggregate directory to examine these first:
             record = DirectoryRecord(self.directory, "users", uid, shortNames=(uid,), enabledForCalendaring=True)
-            record.enabled = True
             self.directory._tmpRecords["shortNames"][uid] = record
             self.directory._tmpRecords["uids"][uid] = record
 
+        # Override augments settings for this record
+        record.enabled = True
+        record.enabledForCalendaring = True
+        record.enabledForAddressBooks = True
+
         cua = "urn:uuid:%s" % (uid,)
 
         principalCollection = self.directory.principalCollection
@@ -872,76 +876,98 @@
 
                         for childName in childNames:
 
-                            childResource = (yield collection.getChild(childName))
-                            # Allways delete inbox items
-                            if self.completely or collName == "inbox":
-                                action = self.CANCELEVENT_SHOULD_DELETE
-                            else:
-                                event = (yield childResource.iCalendar())
-                                event = perUserFilter.filter(event)
-                                action = self._cancelEvent(event, self.when, cua)
-
-                            uri = "/calendars/__uids__/%s/%s/%s" % (uid, collName, childName)
-                            request.path = uri
-                            if action == self.CANCELEVENT_MODIFIED:
-                                count += 1
-                                request._rememberResource(childResource, uri)
-                                storer = StoreCalendarObjectResource(
-                                    request=request,
-                                    destination=childResource,
-                                    destination_uri=uri,
-                                    destinationcal=True,
-                                    destinationparent=collection,
-                                    calendar=str(event),
+                            try:
+                                perresource_request = FakeRequest(self.root, None, None)
+                                perresource_request.checkedSACL = True
+                                perresource_request.authnUser = perresource_request.authzUser = davxml.Principal( 
+                                        davxml.HRef.fromString("/principals/__uids__/%s/" % (uid,))
                                 )
-                                if self.verbose:
-                                    if self.dryrun:
-                                        print("Would modify: %s" % (uri,))
-                                    else:
-                                        print("Modifying: %s" % (uri,))
-                                if not self.dryrun:
-                                    result = (yield storer.run())
+                                perresource_request._rememberResource(collection,
+                                    "/calendars/__uids__/%s/%s/" % (uid, collName))
 
-                            elif action == self.CANCELEVENT_SHOULD_DELETE:
-                                incrementCount = self.dryrun
-                                request._rememberResource(childResource, uri)
-                                if self.verbose:
-                                    if self.dryrun:
-                                        print("Would delete: %s" % (uri,))
-                                    else:
-                                        print("Deleting: %s" % (uri,))
-                                if not self.dryrun:
-                                    retry = False
-                                    try:
-                                        result = (yield childResource.storeRemove(request, self.doimplicit, uri))
-                                        if result != NO_CONTENT:
-                                            print("Error deleting %s/%s/%s: %s" % (uid,
-                                                collName, childName, result))
-                                            retry = True
+                                childResource = (yield collection.getChild(childName))
+
+                                # Allways delete inbox items
+                                if self.completely or collName == "inbox":
+                                    action = self.CANCELEVENT_SHOULD_DELETE
+                                else:
+                                    event = (yield childResource.iCalendar())
+                                    event = perUserFilter.filter(event)
+                                    action = self._cancelEvent(event, self.when, cua)
+
+                                uri = "/calendars/__uids__/%s/%s/%s" % (uid, collName, childName)
+                                perresource_request.path = uri
+                                if action == self.CANCELEVENT_MODIFIED:
+                                    perresource_request._rememberResource(childResource, uri)
+                                    storer = StoreCalendarObjectResource(
+                                        request=perresource_request,
+                                        destination=childResource,
+                                        destination_uri=uri,
+                                        destinationcal=True,
+                                        destinationparent=collection,
+                                        calendar=str(event),
+                                    )
+                                    if self.verbose:
+                                        if self.dryrun:
+                                            print("Would modify: %s" % (uri,))
                                         else:
-                                            incrementCount = True
+                                            print("Modifying: %s" % (uri,))
+                                    if not self.dryrun:
+                                        result = (yield storer.run())
+                                    count += 1
 
-                                    except Exception, e:
-                                        print("Exception deleting %s/%s/%s: %s" % (uid,
-                                            collName, childName, str(e)))
-                                        retry = True
-
-                                    if retry and self.doimplicit:
-                                        # Try again with implicit scheduling off
-                                        print("Retrying deletion of %s/%s/%s with implicit scheduling turned off" % (uid, collName, childName))
+                                elif action == self.CANCELEVENT_SHOULD_DELETE:
+                                    incrementCount = self.dryrun
+                                    perresource_request._rememberResource(childResource, uri)
+                                    if self.verbose:
+                                        if self.dryrun:
+                                            print("Would delete: %s" % (uri,))
+                                        else:
+                                            print("Deleting: %s" % (uri,))
+                                    if not self.dryrun:
+                                        retry = False
                                         try:
-                                            result = (yield childResource.storeRemove(request, False, uri))
+                                            result = (yield childResource.storeRemove(perresource_request, self.doimplicit, uri))
                                             if result != NO_CONTENT:
                                                 print("Error deleting %s/%s/%s: %s" % (uid,
                                                     collName, childName, result))
+                                                retry = True
                                             else:
                                                 incrementCount = True
+
                                         except Exception, e:
-                                            print("Still couldn't delete %s/%s/%s even with implicit scheduling turned off: %s" % (uid, collName, childName, str(e)))
+                                            print("Exception deleting %s/%s/%s: %s" % (uid,
+                                                collName, childName, str(e)))
+                                            retry = True
 
-                                if incrementCount:
-                                    count += 1
+                                        if retry and self.doimplicit:
+                                            # Try again with implicit scheduling off
+                                            print("Retrying deletion of %s/%s/%s with implicit scheduling turned off" % (uid, collName, childName))
+                                            try:
+                                                result = (yield childResource.storeRemove(perresource_request, False, uri))
+                                                if result != NO_CONTENT:
+                                                    print("Error deleting %s/%s/%s: %s" % (uid,
+                                                        collName, childName, result))
+                                                else:
+                                                    incrementCount = True
+                                            except Exception, e:
+                                                print("Still couldn't delete %s/%s/%s even with implicit scheduling turned off: %s" % (uid, collName, childName, str(e)))
 
+                                    if incrementCount:
+                                        count += 1
+                                txn = getattr(perresource_request, "_newStoreTransaction", None)
+                                # Commit
+                                if txn is not None:
+                                    (yield txn.commit())
+
+                            except Exception, e:
+                                # Abort
+                                txn = getattr(perresource_request, "_newStoreTransaction", None)
+                                if txn is not None:
+                                    (yield txn.abort())
+                                raise e
+
+
             txn = getattr(request, "_newStoreTransaction", None)
             # Commit
             if txn is not None:

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/push.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/push.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/push.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -19,39 +19,18 @@
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.cmdline import utilityMain
 from errno import ENOENT, EACCES
-from getopt import getopt, GetoptError
+from argparse import ArgumentParser
 from twext.python.log import Logger
 from twisted.application.service import Service
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks
 from twistedcaldav.config import config, ConfigurationError
-import os
 import sys
 import time
 
 log = Logger()
 
-def usage(e=None):
 
-    name = os.path.basename(sys.argv[0])
-    print("usage: %s [options] [user ...]" % (name,))
-    print("")
-    print("  Display Apple Push Notification subscriptions")
-    print("")
-    print("options:")
-    print("  -h --help: print this help and exit")
-    print("  -f --config <path>: Specify caldavd.plist configuration path")
-    print("  -D --debug: debug logging")
-    print("")
-
-    if e:
-        sys.stderr.write("%s\n" % (e,))
-        sys.exit(64)
-    else:
-        sys.exit(0)
-
-
-
 class WorkerService(Service):
 
     def __init__(self, store):
@@ -109,45 +88,18 @@
 
 def main():
 
-    try:
-        (optargs, args) = getopt(
-            sys.argv[1:], "Df:h", [
-                "config=",
-                "help",
-                "debug",
-            ],
-        )
-    except GetoptError, e:
-        usage(e)
+    parser = ArgumentParser(description='Display Apple Push Notification subscriptions')
+    parser.add_argument('-f', '--config', dest='configFileName', metavar='CONFIGFILE', help='caldavd.plist configuration file path')
+    parser.add_argument('-d', '--debug', action='store_true', help='show debug logging')
+    parser.add_argument('user', help='one or more users to display', nargs='+') # Required
+    args = parser.parse_args()
 
-    #
-    # Get configuration
-    #
-    configFileName = None
-    debug = False
+    DisplayAPNSubscriptions.users = args.user
 
-    for opt, arg in optargs:
-        if opt in ("-h", "--help"):
-            usage()
-
-        elif opt in ("-f", "--config"):
-            configFileName = arg
-
-        if opt in ("-d", "--debug"):
-            debug = True
-
-        else:
-            raise NotImplementedError(opt)
-
-    if not args:
-        usage("Not enough arguments")
-
-    DisplayAPNSubscriptions.users = args
-
     utilityMain(
-        configFileName,
+        args.configFileName,
         DisplayAPNSubscriptions,
-        verbose=debug,
+        verbose=args.debug,
     )
 
 

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/gateway/caldavd.plist	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/gateway/caldavd.plist	2013-03-15 21:47:42 UTC (rev 10937)
@@ -34,6 +34,14 @@
     <key>ServerHostName</key>
     <string></string> <!-- The hostname clients use when connecting -->
 
+    <!-- Enable Calendars -->
+    <key>EnableCalDAV</key>
+    <true/>
+
+    <!-- Enable AddressBooks -->
+    <key>EnableCardDAV</key>
+    <true/>
+
     <!-- HTTP port [0 = disable HTTP] -->
     <key>HTTPPort</key>
     <integer>8008</integer>
@@ -87,7 +95,7 @@
 
     <!-- Configuration root -->
     <key>ConfigRoot</key>
-    <string>/etc/caldavd</string>
+    <string>config</string>
 
     <!-- Log root -->
     <key>LogRoot</key>
@@ -496,65 +504,31 @@
 
       <key>Services</key>
       <dict>
-        <key>SimpleLineNotifier</key>
-        <dict>
-          <!-- Simple line notification service (for testing) -->
-          <key>Service</key>
-          <string>twistedcaldav.notify.SimpleLineNotifierService</string>
-          <key>Enabled</key>
-          <false/>
-          <key>Port</key>
-          <integer>62308</integer>
-        </dict>
 
-        <key>XMPPNotifier</key>
+        <key>APNS</key>
         <dict>
-          <!-- XMPP notification service -->
-          <key>Service</key>
-          <string>twistedcaldav.notify.XMPPNotifierService</string>
           <key>Enabled</key>
           <false/>
-
-          <!-- XMPP host and port to contact -->
-          <key>Host</key>
-          <string>xmpp.host.name</string>
-          <key>Port</key>
-          <integer>5222</integer>
-
-          <!-- Jabber ID and password for the server -->
-          <key>JID</key>
-          <string>jid at xmpp.host.name/resource</string>
-          <key>Password</key>
-          <string>password_goes_here</string>
-
-          <!-- PubSub service address -->
-          <key>ServiceAddress</key>
-          <string>pubsub.xmpp.host.name</string>
-
-          <key>NodeConfiguration</key>
+          <key>EnableStaggering</key>
+          <true/>
+          <key>StaggerSeconds</key>
+          <integer>5</integer>
+          <key>CalDAV</key>
           <dict>
-            <key>pubsub#deliver_payloads</key>
-            <string>1</string>
-            <key>pubsub#persist_items</key>
-            <string>1</string>
+            <key>CertificatePath</key>
+            <string>/example/calendar.cer</string>
+            <key>PrivateKeyPath</key>
+            <string>/example/calendar.pem</string>
           </dict>
-
-          <!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) -->
-          <key>KeepAliveSeconds</key>
-          <integer>120</integer>
-
-          <!-- Sends a pubsub publish to a particular heartbeat node at this interval -->
-          <key>HeartbeatMinutes</key>
-          <integer>30</integer>
-
-          <!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) -->
-          <key>AllowedJIDs</key>
-          <array>
-            <!--
-            <string>*.example.com</string>
-             -->
-          </array>
+          <key>CardDAV</key>
+          <dict>
+            <key>CertificatePath</key>
+            <string>/example/contacts.cer</string>
+            <key>PrivateKeyPath</key>
+            <string>/example/contacts.pem</string>
+          </dict>
         </dict>
+
       </dict>
     </dict>
 
@@ -764,5 +738,12 @@
     </dict>
 
 
+    <key>Includes</key>
+    <array>
+        <string>%(WritablePlist)s</string>
+    </array>
+    <key>WritableConfigFile</key>
+    <string>%(WritablePlist)s</string>
+
   </dict>
 </plist>

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/test_gateway.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/test/test_gateway.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -29,10 +29,10 @@
 from calendarserver.tools.util import getDirectory
 
 
-class GatewayTestCase(TestCase):
+class RunCommandTestCase(TestCase):
 
     def setUp(self):
-        super(GatewayTestCase, self).setUp()
+        super(RunCommandTestCase, self).setUp()
 
         testRoot = os.path.join(os.path.dirname(__file__), "gateway")
         templateName = os.path.join(testRoot, "caldavd.plist")
@@ -42,6 +42,7 @@
 
         newConfig = template % {
             "ServerRoot" : os.path.abspath(config.ServerRoot),
+            "WritablePlist" : os.path.join(os.path.abspath(config.ConfigRoot), "caldavd-writable.plist"),
         }
         configFilePath = FilePath(os.path.join(config.ConfigRoot, "caldavd.plist"))
         configFilePath.setContent(newConfig)
@@ -71,7 +72,8 @@
         return d
 
     @inlineCallbacks
-    def runCommand(self, command, error=False):
+    def runCommand(self, command, error=False,
+        script="calendarserver_command_gateway"):
         """
         Run the given command by feeding it as standard input to
         calendarserver_command_gateway in a subprocess.
@@ -82,9 +84,9 @@
 
         sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
         python = sys.executable
-        gateway = os.path.join(sourceRoot, "bin", "calendarserver_command_gateway")
+        script = os.path.join(sourceRoot, "bin", script)
 
-        args = [python, gateway, "-f", self.configFileName]
+        args = [python, script, "-f", self.configFileName]
         if error:
             args.append("--error")
 
@@ -101,6 +103,9 @@
 
         returnValue(plist)
 
+
+class GatewayTestCase(RunCommandTestCase):
+
     @inlineCallbacks
     def test_getLocationList(self):
         results = yield self.runCommand(command_getLocationList)
@@ -287,6 +292,7 @@
         self.assertEquals(results["result"]["RetainDays"], 365)
 
 
+
 command_addReadProxy = """<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">

Modified: CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/upgrade.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/upgrade.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/calendarserver/tools/upgrade.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -15,12 +15,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from __future__ import print_function
 
 """
 This tool allows any necessary upgrade to complete, then exits.
 """
 
+from __future__ import print_function
 import os
 import sys
 import time

Modified: CalendarServer/branches/users/gaya/directorybacker/conf/auth/resources-test.xml
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/conf/auth/resources-test.xml	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/conf/auth/resources-test.xml	2013-03-15 21:47:42 UTC (rev 10937)
@@ -35,7 +35,7 @@
     <uid>mercury</uid>
     <guid>mercury</guid>
     <password>test</password>
-    <name>Mecury Conference Room, Building 1, 2nd Floor</name>
+    <name>Mercury Conference Room, Building 1, 2nd Floor</name>
   </location>
   <location>
     <uid>venus</uid>

Modified: CalendarServer/branches/users/gaya/directorybacker/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/conf/caldavd-apple.plist	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/conf/caldavd-apple.plist	2013-03-15 21:47:42 UTC (rev 10937)
@@ -522,5 +522,13 @@
     <key>OpenDirectoryModule</key>
     <string>calendarserver.platform.darwin.od.opendirectory</string>
 
+    <key>Includes</key>
+    <array>
+        <string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>
+        <string>/Library/Server/Calendar and Contacts/Config/caldavd-user.plist</string>
+    </array>
+    <key>WritableConfigFile</key>
+    <string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>
+
   </dict>
 </plist>

Deleted: CalendarServer/branches/users/gaya/directorybacker/contrib/migration/__init__.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/contrib/migration/__init__.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/contrib/migration/__init__.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,15 +0,0 @@
-##
-# Copyright (c) 2010-2013 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.
-##

Deleted: CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarcommonextra.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarcommonextra.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarcommonextra.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,187 +0,0 @@
-#!/usr/bin/env python
-#
-# CommonExtra script for calendar server.
-#
-# Copyright (c) 2012-2013 Apple Inc.  All Rights Reserved.
-#
-# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of.  You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import datetime
-import subprocess
-from plistlib import readPlist, writePlist
-
-LOG = "/Library/Logs/Migration/calendarmigrator.log"
-SERVER_APP_ROOT = "/Applications/Server.app/Contents/ServerRoot"
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-CALDAVD_PLIST = "%s/Config/caldavd.plist" % (CALENDAR_SERVER_ROOT,)
-SERVER_ADMIN = "%s/usr/sbin/serveradmin" % (SERVER_APP_ROOT,)
-CERT_ADMIN = "/Applications/Server.app/Contents/ServerRoot/usr/sbin/certadmin"
-PGDUMP = "%s/usr/bin/pg_dump" % (SERVER_APP_ROOT,)
-DROPDB = "%s/usr/bin/dropdb" % (SERVER_APP_ROOT,)
-POSTGRES_SERVICE_NAME = "postgres_server"
-PGSOCKETDIR = "/Library/Server/PostgreSQL For Server Services/Socket"
-USERNAME      = "caldav"
-DATABASENAME  = "caldav"
-DATADUMPFILENAME = "%s/DataDump.sql" % (CALENDAR_SERVER_ROOT,)
-
-def log(msg):
-    try:
-        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
-        msg = "calendarcommonextra: %s %s" % (timestamp, msg)
-        print(msg) # so it appears in Setup.log
-        with open(LOG, 'a') as output:
-            output.write("%s\n" % (msg,)) # so it appears in our log
-    except IOError:
-        # Could not write to log
-        pass
-
-
-def startPostgres():
-    """
-    Start postgres via serveradmin
-
-    This will block until postgres is up and running
-    """
-    log("Starting %s via %s" % (POSTGRES_SERVICE_NAME, SERVER_ADMIN))
-    ret = subprocess.call([SERVER_ADMIN, "start", POSTGRES_SERVICE_NAME])
-    log("serveradmin exited with %d" % (ret,))
-
-def stopPostgres():
-    """
-    Stop postgres via serveradmin
-    """
-    log("Stopping %s via %s" % (POSTGRES_SERVICE_NAME, SERVER_ADMIN))
-    ret = subprocess.call([SERVER_ADMIN, "stop", POSTGRES_SERVICE_NAME])
-    log("serveradmin exited with %d" % (ret,))
-
-
-def dumpOldDatabase(dumpFile):
-    """
-    Use pg_dump to dump data to dumpFile
-    """
-
-    cmdArgs = [
-        PGDUMP,
-        "-h", PGSOCKETDIR,
-        "--username=%s" % (USERNAME,),
-        "--inserts",
-        "--no-privileges",
-        "--file=%s" % (dumpFile,),
-        DATABASENAME
-    ]
-    try:
-        log("Dumping data to %s" % (dumpFile,))
-        log("Executing: %s" % (" ".join(cmdArgs)))
-        out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
-        log(out)
-        return True
-    except subprocess.CalledProcessError, e:
-        log(e.output)
-        return False
-
-
-def dropOldDatabase():
-    """
-    Use dropdb to delete the caldav database from the shared postgres server
-    """
-
-    cmdArgs = [
-        DROPDB,
-        "-h", PGSOCKETDIR,
-        "--username=%s" % (USERNAME,),
-        DATABASENAME
-    ]
-    try:
-        log("\nDropping %s database" % (DATABASENAME,))
-        log("Executing: %s" % (" ".join(cmdArgs)))
-        out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
-        log(out)
-        return True
-    except subprocess.CalledProcessError, e:
-        log(e.output)
-        return False
-
-
-def getDefaultCert():
-    """
-    Ask certadmin for default cert
-    @returns: path to default certificate, or empty string if no default
-    @rtype: C{str}
-    """
-    child = subprocess.Popen(
-        args=[CERT_ADMIN, "--default-certificate-path"],
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-    )
-    output, error = child.communicate()
-    if child.returncode:
-        log("Error looking up default certificate (%d): %s" % (child.returncode, error))
-        return ""
-    else:
-        certPath = output.strip()
-        log("Default certificate is: %s" % (certPath,))
-        return certPath
-
-def updateSettings(settings, otherCert):
-    """
-    Replace SSL settings based on otherCert path
-    """
-    basePath = otherCert[:-len("cert.pem")]
-    log("Base path is %s" % (basePath,))
-
-    log("Setting SSLCertificate to %s" % (otherCert,))
-    settings["SSLCertificate"] = otherCert
-
-    otherChain = basePath + "chain.pem"
-    log("Setting SSLAuthorityChain to %s" % (otherChain,))
-    settings["SSLAuthorityChain"] = otherChain
-
-    otherKey = basePath + "key.pem"
-    log("Setting SSLPrivateKey to %s" % (otherKey,))
-    settings["SSLPrivateKey"] = otherKey
-
-    settings["EnableSSL"] = True
-    settings["RedirectHTTPToHTTPS"] = True
-    settings.setdefault("Authentication", {}).setdefault("Basic", {})["Enabled"] = True
-
-def setCert(plistPath, otherCert):
-    """
-    Replace SSL settings in plist at plistPath based on otherCert path
-    """
-    log("Reading plist %s" % (plistPath,))
-    plist = readPlist(plistPath)
-    log("Read in plist %s" % (plistPath,))
-
-    updateSettings(plist, otherCert)
-
-    log("Writing plist %s" % (plistPath,))
-    writePlist(plist, plistPath)
-
-def isSSLEnabled(plistPath):
-    """
-    Examine plist for EnableSSL
-    """
-    log("Reading plist %s" % (plistPath,))
-    plist = readPlist(plistPath)
-    return plist.get("EnableSSL", False)
-
-def main():
-    startPostgres()
-    if dumpOldDatabase(DATADUMPFILENAME):
-        dropOldDatabase()
-    stopPostgres()
-
-    if not isSSLEnabled(CALDAVD_PLIST):
-        defaultCertPath = getDefaultCert()
-        log("Default cert path: %s" % (defaultCertPath,))
-        if defaultCertPath:
-            setCert(CALDAVD_PLIST, defaultCertPath)
-
-
-if __name__ == "__main__":
-    main()

Deleted: CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendardemotion.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendardemotion.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendardemotion.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,38 +0,0 @@
-#!/usr/bin/env python
-#
-# UninstallExtra script for calendar server.
-#
-# Copyright (c) 2011-2013 Apple Inc.  All Rights Reserved.
-#
-# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of.  You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import os
-from plistlib import readPlist, writePlist
-
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-DEST_CONFIG_DIR = "%s/Config" % (CALENDAR_SERVER_ROOT,)
-CALDAVD_PLIST = "caldavd.plist"
-
-def main():
-
-    plistPath = os.path.join(DEST_CONFIG_DIR, CALDAVD_PLIST)
-
-    if os.path.exists(plistPath):
-        try:
-            # Turn off services
-            plistData = readPlist(plistPath)
-            plistData["EnableCalDAV"] = False
-            plistData["EnableCardDAV"] = False
-            writePlist(plistData, plistPath)
-
-        except Exception, e:
-            print("Unable to disable services in %s: %s" % (plistPath, e))
-
-
-if __name__ == '__main__':
-    main()

Deleted: CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarmigrator.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarmigrator.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,900 +0,0 @@
-#!/usr/bin/env python
-#
-# MigrationExtra script to maintain the enabled/disabled state of the
-# calendar server.
-#
-# This script examines the launchd preferences from the previous system
-# (also taking into account the overrides.plist) and then invokes serveradmin
-# to start/stop calendar server.
-#
-# The only argument this script currently cares about is --sourceRoot, which
-# should point to the root of the previous system.
-#
-# Copyright (c) 2005-2013 Apple Inc.  All Rights Reserved.
-#
-# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of.  You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-from __future__ import with_statement
-
-import datetime
-import grp
-import optparse
-import os
-import pwd
-import shutil
-import subprocess
-import sys
-
-from plistlib import readPlist, readPlistFromString, writePlist
-
-SERVER_APP_ROOT = "/Applications/Server.app/Contents/ServerRoot"
-LOG = "/Library/Logs/Migration/calendarmigrator.log"
-CALDAVD_CONFIG_DIR = "private/etc/caldavd"
-CARDDAVD_CONFIG_DIR = "private/etc/carddavd"
-CALDAVD_PLIST = "caldavd.plist"
-CARDDAVD_PLIST = "carddavd.plist"
-NEW_SERVER_DIR = "Calendar and Contacts"
-NEW_SERVER_ROOT = "/Library/Server/" + NEW_SERVER_DIR
-NEW_CONFIG_DIR = "Library/Server/" + NEW_SERVER_DIR + "/Config"
-LOG_DIR = "var/log/caldavd"
-DITTO = "/usr/bin/ditto"
-RESOURCE_MIGRATION_TRIGGER = "trigger_resource_migration"
-
-# For looking up previous run state
-CALDAV_LAUNCHD_KEY = "org.calendarserver.calendarserver"
-CARDDAV_LAUNCHD_KEY = "org.addressbookserver.addressbookserver"
-LAUNCHD_OVERRIDES = "var/db/launchd.db/com.apple.launchd/overrides.plist"
-LAUNCHD_PREFS_DIR = "System/Library/LaunchDaemons"
-SERVER_ADMIN = "%s/usr/sbin/serveradmin" % (SERVER_APP_ROOT,)
-
-# Processed by mergePlist
-specialKeys = """
-Authentication
-BindHTTPPorts
-BindSSLPorts
-DataRoot
-DirectoryService
-DocumentRoot
-EnableSSL
-HTTPPort
-RedirectHTTPToHTTPS
-SSLAuthorityChain
-SSLCertificate
-SSLPort
-SSLPrivateKey
-""".split()
-
-# Ignored by mergePlist
-ignoredKeys = """
-EnableFindSharedReport
-EnableNotifications
-MaxAddressBookMultigetHrefs
-MaxAddressBookQueryResults
-PythonDirector
-Verbose
-""".split()
-
-
-def main():
-
-    optionParser = optparse.OptionParser()
-
-    optionParser.add_option('--purge', choices=('0', '1'),
-        metavar='[0|1]',
-        help='remove old files after migration (IGNORED)')
-
-    optionParser.add_option('--sourceRoot', type='string',
-        metavar='DIR',
-        help='path to the root of the system to migrate')
-
-    optionParser.add_option('--sourceType', type='string',
-        metavar='[System|TimeMachine]',
-        help='migration source type (IGNORED)')
-
-    optionParser.add_option('--sourceVersion', type='string',
-        metavar='10.X.X',
-        help='version number of previous system')
-
-    optionParser.add_option('--targetRoot', type='string',
-        metavar='DIR',
-        help='path to the root of the new system',
-        default='/')
-
-    optionParser.add_option('--language',
-        help='language identifier (IGNORED)',
-        default="en")
-
-    (options, args) = optionParser.parse_args()
-    log("Options: %s" % (options,))
-
-    if options.sourceRoot and options.sourceVersion:
-
-        if os.path.exists(options.sourceRoot):
-
-            enableCalDAV, enableCardDAV = examineRunState(options)
-
-            # Pull values out of previous plists
-            (
-                oldServerRootValue,
-                oldCalDocumentRootValue,
-                oldCalDataRootValue,
-                oldABDocumentRootValue,
-                uid,
-                gid
-            ) = examinePreviousSystem(
-                options.sourceRoot,
-                options.targetRoot
-            )
-
-            # Copy data as needed
-            (
-                newServerRoot,
-                newServerRootValue,
-                newDataRootValue
-            ) = relocateData(
-                options.sourceRoot,
-                options.targetRoot,
-                options.sourceVersion,
-                oldServerRootValue,
-                oldCalDocumentRootValue,
-                oldCalDataRootValue,
-                oldABDocumentRootValue,
-                uid,
-                gid
-            )
-
-            # Combine old and new plists
-            migrateConfiguration(
-                options,
-                newServerRootValue,
-                newDataRootValue,
-                enableCalDAV,
-                enableCardDAV
-            )
-
-            # Create log directory
-            try:
-                logDir = os.path.join(options.targetRoot, LOG_DIR)
-                os.mkdir(logDir, 0755)
-            except OSError:
-                # Already exists
-                pass
-            # Set ownership
-            os.chown(logDir, uid, gid)
-
-            # Trigger migration of locations and resources from OD
-            triggerResourceMigration(newServerRoot)
-
-            # TODO: instead of starting now, leave breadcrumbs for
-            # the commonextra to start the service, so that data can
-            # be dumped from the old Postgres to a file which will
-            # be executed by calendar server when it next starts up.
-
-            # setRunState(options, enableCalDAV, enableCardDAV)
-
-    else:
-        log("ERROR: --sourceRoot and --sourceVersion must be specified")
-        sys.exit(1)
-
-
-def examineRunState(options):
-    """
-    Try to determine whether the CalDAV and CardDAV services were running in
-    previous system.
-
-    @return: a tuple of booleans: whether CalDAV was enabled, and whether
-    CardDAV was enabled
-    """
-
-    enableCalDAV = None
-    enableCardDAV = None
-
-    try:
-        disabled = isServiceDisabled(options.sourceRoot, CALDAV_LAUNCHD_KEY)
-        enableCalDAV = not disabled
-        log("Calendar service '%s' was previously %s" %
-            (CALDAV_LAUNCHD_KEY, "disabled" if disabled else "enabled"))
-    except ServiceStateError, e:
-        log("Couldn't determine previous state of calendar service '%s': %s" %
-            (CALDAV_LAUNCHD_KEY, e))
-
-    try:
-        disabled = isServiceDisabled(options.sourceRoot, CARDDAV_LAUNCHD_KEY)
-        enableCardDAV = not disabled
-        log("Addressbook service '%s' was previously %s" %
-            (CARDDAV_LAUNCHD_KEY, "disabled" if disabled else "enabled"))
-    except ServiceStateError, e:
-        log("Couldn't determine previous state of addressbook service '%s': %s" %
-            (CARDDAV_LAUNCHD_KEY, e))
-
-    if enableCalDAV:
-        # Check previous plist in case previous system was Lion, since there
-        # is now only one launchd key for both services
-        oldCalDAVPlistPath = os.path.join(options.sourceRoot,
-            CALDAVD_CONFIG_DIR, CALDAVD_PLIST)
-        if os.path.exists(oldCalDAVPlistPath):
-            log("Examining previous caldavd.plist for EnableCalDAV and EnableCardDAV: %s" % (oldCalDAVPlistPath,))
-            oldCalDAVDPlist = readPlist(oldCalDAVPlistPath)
-            if "EnableCalDAV" in oldCalDAVDPlist:
-                enableCalDAV = oldCalDAVDPlist["EnableCalDAV"]
-                log("Based on caldavd.plist, setting EnableCalDAV to %s" % (enableCalDAV,))
-            if "EnableCardDAV" in oldCalDAVDPlist:
-                enableCardDAV = oldCalDAVDPlist["EnableCardDAV"]
-                log("Based on caldavd.plist, setting EnableCardDAV to %s" % (enableCardDAV,))
-
-    # A value of None means we weren't able to determine, so default to off
-    if enableCalDAV is None:
-        enableCalDAV = False
-    if enableCardDAV is None:
-        enableCardDAV = False
-
-    return (enableCalDAV, enableCardDAV)
-
-
-def setRunState(options, enableCalDAV, enableCardDAV):
-    """
-    Use serveradmin to launch the service if needed.
-    """
-
-    if enableCalDAV or enableCardDAV:
-        serviceName = "calendar" if enableCalDAV else "addressbook"
-        log("Starting service via serveradmin start %s" % (serviceName,))
-        ret = subprocess.call([SERVER_ADMIN, "start", serviceName])
-        log("serveradmin exited with %d" % (ret,))
-
-
-def isServiceDisabled(source, service, launchdOverrides=LAUNCHD_OVERRIDES,
-    launchdPrefsDir=LAUNCHD_PREFS_DIR):
-    """
-    Returns whether or not a service is disabled
-
-    @param source: System root to examine
-    @param service: launchd key representing service
-    @return: True if service is disabled, False if enabled
-    """
-
-    overridesPath = os.path.join(source, launchdOverrides)
-    if os.path.isfile(overridesPath):
-        try:
-            overrides = readPlist(overridesPath)
-        except Exception, e:
-            raise ServiceStateError("Could not parse %s : %s" %
-                (overridesPath, str(e)))
-
-        try:
-            return overrides[service]['Disabled']
-        except KeyError:
-            # Key is not in the overrides.plist, continue on
-            pass
-
-    prefsPath = os.path.join(source, launchdPrefsDir, "%s.plist" % service)
-    if os.path.isfile(prefsPath):
-        try:
-            prefs = readPlist(prefsPath)
-        except Exception, e:
-            raise ServiceStateError("Could not parse %s : %s" %
-                (prefsPath, str(e)))
-        try:
-            return prefs['Disabled']
-        except KeyError:
-            return False
-
-    raise ServiceStateError("Neither %s nor %s exist" %
-        (overridesPath, prefsPath))
-
-
-class ServiceStateError(Exception):
-    """
-    Could not determine service state
-    """
-
-
-
-def migrateConfiguration(options, newServerRootValue, newDataRootValue, enableCalDAV, enableCardDAV):
-    """
-    Copy files/directories/symlinks from previous system's /etc/caldavd
-    and /etc/carddavd
-
-    Skips anything ending in ".default".
-    Regular files overwrite copies in new system.
-    Directories and symlinks only copied over if they don't overwrite anything.
-    """
-
-    newConfigDir = os.path.join(options.targetRoot, NEW_CONFIG_DIR)
-    newConfigFile = os.path.join(newConfigDir, CALDAVD_PLIST)
-
-    # Create config directory if it doesn't exist
-    if not os.path.exists(newConfigDir):
-        os.mkdir(newConfigDir)
-
-    defaultConfig = os.path.join(SERVER_APP_ROOT, CALDAVD_CONFIG_DIR, CALDAVD_PLIST)
-    if os.path.exists(defaultConfig) and not os.path.exists(newConfigFile):
-        log("Copying default config file %s to %s" % (defaultConfig, newConfigFile))
-        shutil.copy2(defaultConfig, newConfigFile)
-
-    for configDir in (NEW_CONFIG_DIR, CALDAVD_CONFIG_DIR, CARDDAVD_CONFIG_DIR):
-
-        oldConfigDir = os.path.join(options.sourceRoot, configDir)
-        if not os.path.exists(oldConfigDir):
-            log("Old configuration directory does not exist: %s" % (oldConfigDir,))
-            continue
-
-        log("Copying configuration files from %s to %s" % (oldConfigDir, newConfigDir))
-
-        for name in os.listdir(oldConfigDir):
-
-            if not (name.endswith(".default") or name in (CALDAVD_PLIST, CARDDAVD_PLIST)):
-
-                oldPath = os.path.join(oldConfigDir, name)
-                newPath = os.path.join(newConfigDir, name)
-
-                if os.path.islink(oldPath) and not os.path.exists(newPath):
-                    # Recreate the symlink if it won't overwrite an existing file
-                    link = os.readlink(oldPath)
-                    log("Symlinking %s to %s" % (newPath, link))
-                    os.symlink(link, newPath)
-
-                elif os.path.isfile(oldPath):
-                    # Copy the file over, overwriting copy in newConfigDir
-                    log("Copying file %s to %s" % (oldPath, newConfigDir))
-                    shutil.copy2(oldPath, newConfigDir)
-
-                elif os.path.isdir(oldPath) and not os.path.exists(newPath):
-                    # Copy the dir over, but only if new one doesn't exist
-                    log("Copying directory %s to %s" % (oldPath, newPath))
-                    shutil.copytree(oldPath, newPath, symlinks=True)
-
-
-    # Migrate certain settings from the old plists to new:
-
-    oldCalDAVPlistPath = os.path.join(options.sourceRoot, CALDAVD_CONFIG_DIR,
-        CALDAVD_PLIST)
-    if os.path.exists(oldCalDAVPlistPath):
-        oldCalDAVDPlist = readPlist(oldCalDAVPlistPath)
-    else:
-        oldCalDAVDPlist = { }
-
-    oldCardDAVDPlistPath = os.path.join(options.sourceRoot, CARDDAVD_CONFIG_DIR,
-        CARDDAVD_PLIST)
-    if os.path.exists(oldCardDAVDPlistPath):
-        oldCardDAVDPlist = readPlist(oldCardDAVDPlistPath)
-    else:
-        oldCardDAVDPlist = { }
-
-    if os.path.exists(newConfigFile):
-        newCalDAVDPlist = readPlist(newConfigFile)
-    else:
-        newCalDAVDPlist = { }
-
-    log("Processing %s and %s" % (oldCalDAVPlistPath, oldCardDAVDPlistPath))
-    adminChanges = mergePlist(oldCalDAVDPlist, oldCardDAVDPlist, newCalDAVDPlist)
-
-    newCalDAVDPlist["ServerRoot"] = newServerRootValue
-    newCalDAVDPlist["DocumentRoot"] = "Documents"
-    newCalDAVDPlist["DataRoot"] = newDataRootValue
-
-    newCalDAVDPlist["EnableCalDAV"] = enableCalDAV
-    newCalDAVDPlist["EnableCardDAV"] = enableCardDAV
-
-    log("Writing %s" % (newConfigFile,))
-    writePlist(newCalDAVDPlist, newConfigFile)
-
-    for key, value in adminChanges:
-        log("Setting %s to %s via serveradmin..." % (key, value))
-        ret = subprocess.call([SERVER_ADMIN, "settings", "calendar:%s=%s" % (key, value)])
-        log("serveradmin exited with %d" % (ret,))
-
-
-
-def mergePlist(caldav, carddav, combined):
-
-    adminChanges = []
-
-    # Copy all non-ignored keys
-    for key in carddav:
-        if key not in ignoredKeys and key not in specialKeys:
-            combined[key] = carddav[key]
-    for key in caldav:
-        if key not in ignoredKeys and key not in specialKeys:
-            combined[key] = caldav[key]
-
-    # Copy all "Authentication" sub-keys
-    if "Authentication" in caldav:
-        if "Authentication" not in combined:
-            combined["Authentication"] = { }
-        for key in caldav["Authentication"]:
-            combined["Authentication"][key] = caldav["Authentication"][key]
-
-        # Reset the wiki settings since URL is only used wieh LionCompatibility
-        combined["Authentication"]["Wiki"] = { "Enabled" : True }
-
-    # Strip out any unknown params from the DirectoryService:
-    if "DirectoryService" in caldav:
-        combined["DirectoryService"] = caldav["DirectoryService"]
-        for key in combined["DirectoryService"]["params"].keys():
-            if key in ("requireComputerRecord",):
-                del combined["DirectoryService"]["params"][key]
-
-    # Disable XMPPNotifier now that we're directly talking to APNS
-    try:
-        XMPPNotifier = caldav["Notifications"]["Services"]["XMPPNotifier"]
-        if XMPPNotifier["Enabled"]:
-            XMPPNotifier["Enabled"] = False
-    except KeyError:
-        pass
-
-    # If XMPP was also previously being routed to APNS, enable APNS
-    EnableAPNS = False
-    try:
-        if caldav["Notifications"]["Services"]["XMPPNotifier"]["CalDAV"]["APSBundleID"]:
-            EnableAPNS = True
-    except KeyError:
-        pass
-    try:
-        if caldav["Notifications"]["Services"]["XMPPNotifier"]["CardDAV"]["APSBundleID"]:
-            EnableAPNS = True
-    except KeyError:
-        pass
-    if EnableAPNS:
-        adminChanges.append(["EnableAPNS", "yes"])
-
-    # Merge ports
-    if not caldav.get("HTTPPort", 0):
-        caldav["HTTPPort"] = 8008
-    if not carddav.get("HTTPPort", 0):
-        carddav["HTTPPort"] = 8800
-    if not caldav.get("SSLPort", 0):
-        caldav["SSLPort"] = 8443
-    if not carddav.get("SSLPort", 0):
-        carddav["SSLPort"] = 8843
-
-    for portType in ["HTTPPort", "SSLPort"]:
-        bindPorts = list(set(caldav.get("Bind%ss" % (portType,), [])).union(set(carddav.get("Bind%ss" % (portType,), []))))
-        for prev in (carddav, caldav):
-            port = prev.get(portType, 0)
-            if port and port not in bindPorts:
-                bindPorts.append(port)
-        bindPorts.sort()
-        combined["Bind%ss" % (portType,)] = bindPorts
-
-    combined["HTTPPort"] = caldav["HTTPPort"]
-    combined["SSLPort"] = caldav["SSLPort"]
-
-    # Was SSL enabled?
-    sslAuthorityChain = ""
-    sslCertificate = ""
-    sslPrivateKey = ""
-    enableSSL = False
-    for prev in (carddav, caldav):
-        if (prev["SSLPort"] and prev.get("SSLCertificate", "")):
-            sslAuthorityChain = prev.get("SSLAuthorityChain", "")
-            sslCertificate = prev.get("SSLCertificate", "")
-            sslPrivateKey = prev.get("SSLPrivateKey", "")
-            enableSSL = True
-
-    combined["SSLAuthorityChain"] = sslAuthorityChain
-    combined["SSLCertificate"] = sslCertificate
-    combined["SSLPrivateKey"] = sslPrivateKey
-    combined["EnableSSL"] = enableSSL
-
-    # If SSL is enabled, redirect HTTP to HTTPS.
-    combined["RedirectHTTPToHTTPS"] = enableSSL
-
-    # New DBType value indicating we launch our own Postgres
-    combined["DBType"] = ""
-
-    # No DSN value since we launch our own Postgres
-    combined["DSN"] = ""
-
-    # Path to SQL file to import previous data from
-    combined["DBImportFile"] = "/Library/Server/Calendar and Contacts/DataDump.sql"
-
-    # ConfigRoot is now always "Config"
-    combined["ConfigRoot"] = "Config"
-
-    # Remove RunRoot and PIDFile keys so they use the new defaults
-    try:
-        del combined["RunRoot"]
-    except:
-        pass
-    try:
-        del combined["PIDFile"]
-    except:
-        pass
-
-    return adminChanges
-
-
-def log(msg):
-    try:
-        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
-        msg = "calendarmigrator: %s %s" % (timestamp, msg)
-        print(msg) # so it appears in Setup.log
-        with open(LOG, 'a') as output:
-            output.write("%s\n" % (msg,)) # so it appears in our log
-    except IOError:
-        # Could not write to log
-        pass
-
-def examinePreviousSystem(sourceRoot, targetRoot, diskAccessor=None):
-    """
-    Examines the old caldavd.plist and carddavd.plist to see where data
-    lives in the previous system.
-    """
-
-    if diskAccessor is None:
-        diskAccessor = DiskAccessor()
-
-    oldServerRootValue = None
-    oldCalDocumentRootValue = None
-    oldCalDataRootValue = None
-    oldABDocumentRootValue = None
-
-    uid = pwd.getpwnam("calendar").pw_uid
-    gid = grp.getgrnam("calendar").gr_gid
-
-    # Try and read old caldavd.plist
-    oldCalConfigDir = os.path.join(sourceRoot, CALDAVD_CONFIG_DIR)
-    oldCalPlistPath = os.path.join(oldCalConfigDir, CALDAVD_PLIST)
-    if diskAccessor.exists(oldCalPlistPath):
-        contents = diskAccessor.readFile(oldCalPlistPath)
-        oldCalPlist = readPlistFromString(contents)
-        log("Found previous caldavd plist at %s" % (oldCalPlistPath,))
-
-        oldServerRootValue = oldCalPlist.get("ServerRoot", None)
-        oldCalDocumentRootValue = oldCalPlist.get("DocumentRoot", None)
-        oldCalDataRootValue = oldCalPlist.get("DataRoot", None)
-
-    else:
-        log("Can't find previous calendar plist at %s" % (oldCalPlistPath,))
-        oldCalPlist = None
-
-    # Try and read old carddavd.plist
-    oldABConfigDir = os.path.join(sourceRoot, CARDDAVD_CONFIG_DIR)
-    oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
-    if diskAccessor.exists(oldABPlistPath):
-        contents = diskAccessor.readFile(oldABPlistPath)
-        oldABPlist = readPlistFromString(contents)
-        log("Found previous carddavd plist at %s" % (oldABPlistPath,))
-
-        oldABDocumentRootValue = oldABPlist.get("DocumentRoot", None)
-    else:
-        log("Can't find previous carddavd plist at %s" % (oldABPlistPath,))
-        oldABPlist = None
-
-    return (
-        oldServerRootValue,
-        oldCalDocumentRootValue,
-        oldCalDataRootValue,
-        oldABDocumentRootValue,
-        uid,
-        gid
-    )
-
-
-def relocateData(sourceRoot, targetRoot, sourceVersion, oldServerRootValue,
-    oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue,
-    uid, gid, diskAccessor=None):
-    """
-    Copy data from sourceRoot to targetRoot, except when data is on another
-    volume in which case we just refer to it there.
-    """
-
-    if diskAccessor is None:
-        diskAccessor = DiskAccessor()
-
-    log("RelocateData: sourceRoot=%s, targetRoot=%s, oldServerRootValue=%s, oldCalDocumentRootValue=%s, oldCalDataRootValue=%s, oldABDocumentRootValue=%s, uid=%d, gid=%d" % (sourceRoot, targetRoot, oldServerRootValue, oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue, uid, gid))
-
-    newServerRootValue = "/Library/Server/Calendar and Contacts"
-    newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
-
-    if sourceVersion < "10.7":
-        oldCalDocumentRootValueProcessed = oldCalDocumentRootValue
-        oldCalDataRootValueProcessed = oldCalDataRootValue
-
-    else:
-        # If there was an old ServerRoot value, process DocumentRoot and
-        # DataRoot because those could be relative to ServerRoot
-
-        if sourceVersion < "10.8":
-            # DocumentRoot and DataRoot are both relative to ServerRoot
-            oldCalDocumentRootValueProcessed = os.path.join(oldServerRootValue,
-                oldCalDocumentRootValue)
-            oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
-                oldCalDataRootValue)
-        else:
-            # DocumentRoot is relative to DataRoot, DataRoot is relative to ServerRoot
-            oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
-                oldCalDataRootValue)
-            oldCalDocumentRootValueProcessed = os.path.join(oldCalDataRootValueProcessed,
-                oldCalDocumentRootValue)
-
-
-    # Set default values for these, possibly overridden below:
-    newDataRootValue = "Data"
-    newDataRoot = absolutePathWithRoot(
-        targetRoot,
-        os.path.join(newServerRootValue, newDataRootValue)
-    )
-    newDocumentRootValue = "Documents"
-    newDocumentRoot = os.path.join(newDataRoot, newDocumentRootValue)
-
-    if sourceVersion < "10.7":
-        # Before 10.7 there was no ServerRoot; DocumentRoot and DataRoot were separate.
-        # Reconfigure so DocumentRoot is under DataRoot is under ServerRoot.  DataRoot
-        # will be /Library/Server/Calendar and Contacts/Data unless old DocumentRoot was on
-        # an external volume, in which case that becomes the new DataRoot and DocumentRoot
-        # moves under DataRoot.
-        # /Library/Server/Calendar and Contacts will be new ServerRoot no matter what.
-
-        if oldCalDocumentRootValueProcessed:
-            if oldCalDocumentRootValueProcessed.startswith("/Volumes/"): # external volume
-                # The old external calendar DocumentRoot becomes the new DataRoot
-                newDataRoot = newDataRootValue = os.path.join(os.path.dirname(oldCalDocumentRootValue.rstrip("/")), "Calendar and Contacts Data")
-                newDocumentRoot = os.path.join(newDataRoot, newDocumentRootValue)
-                # Move aside whatever is there
-                if diskAccessor.exists(newDataRoot):
-                    renameTo = nextAvailable(newDataRoot, "bak", diskAccessor=diskAccessor)
-                    diskAccessor.rename(newDataRoot, renameTo)
-
-                if diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed)):
-                    diskAccessor.ditto(
-                        absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
-                        newDataRoot
-                    )
-                else:
-                    diskAccessor.mkdir(newDataRoot)
-
-                # Move old DocumentRoot under new DataRoot
-                diskAccessor.rename(oldCalDocumentRootValue, newDocumentRoot)
-                diskAccessor.chown(newDataRoot, uid, gid, recursive=True)
-
-            else: # The old calendar DocumentRoot is not external
-                if oldCalDataRootValueProcessed:
-                    if diskAccessor.exists(absolutePathWithRoot(sourceRoot,
-                        oldCalDataRootValueProcessed)):
-                        diskAccessor.ditto(
-                            absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
-                            newDataRoot
-                        )
-                if diskAccessor.exists(absolutePathWithRoot(sourceRoot,
-                    oldCalDocumentRootValueProcessed)):
-                    diskAccessor.ditto(
-                        absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed),
-                        newDocumentRoot
-                    )
-
-        # Old AddressBook DocumentRoot
-        if oldABDocumentRootValue:
-            newAddressBooks = os.path.join(newDocumentRoot, "addressbooks")
-            if oldABDocumentRootValue.startswith("/Volumes/"): # external volume
-                diskAccessor.ditto(
-                    os.path.join(oldABDocumentRootValue, "addressbooks"),
-                    newAddressBooks
-                )
-            elif diskAccessor.exists(
-                absolutePathWithRoot(sourceRoot, oldABDocumentRootValue)
-            ):
-                diskAccessor.ditto(
-                    absolutePathWithRoot(
-                        sourceRoot,
-                        os.path.join(oldABDocumentRootValue, "addressbooks")
-                    ),
-                    os.path.join(newDocumentRoot, "addressbooks")
-                )
-
-
-    elif sourceVersion < "10.8":
-        # Before 10.8, DocumentRoot and DataRoot were relative to ServerRoot
-
-        if oldServerRootValue:
-            if oldServerRootValue.rstrip("/").startswith("/Volumes/"): # external volume
-                log("Using external calendar server root: %s" % (oldServerRootValue,))
-                # ServerRoot needs to be /Library/Server/Calendar and Contacts
-                # Since DocumentRoot is now relative to DataRoot, move DocumentRoot into DataRoot
-                newDataRoot = newDataRootValue = os.path.join(oldServerRootValue, "Data")
-                if not diskAccessor.exists(newDataRoot):
-                    diskAccessor.mkdir(newDataRoot)
-                newDocumentRoot = os.path.join(newDataRootValue, "Documents")
-                if not diskAccessor.exists(newDocumentRoot):
-                    if diskAccessor.exists(os.path.join(oldServerRootValue, "Documents")):
-                        diskAccessor.rename(os.path.join(oldServerRootValue, "Documents"),
-                            newDocumentRoot)
-                    else:
-                        diskAccessor.mkdir(newDocumentRoot)
-            elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldServerRootValue)):
-                log("Copying calendar server root: %s" % (newServerRoot,))
-                diskAccessor.ditto(
-                    absolutePathWithRoot(sourceRoot, oldServerRootValue),
-                    newServerRoot
-                )
-                newDataRoot = os.path.join(newServerRoot, "Data")
-                if not diskAccessor.exists(newDataRoot):
-                    diskAccessor.mkdir(newDataRoot)
-                newDocumentRoot = os.path.join(newDataRoot, "Documents")
-                if not diskAccessor.exists(newDocumentRoot):
-                    if diskAccessor.exists(os.path.join(newServerRoot, "Documents")):
-                        log("Moving Documents into Data root: %s" % (newDataRoot,))
-                        diskAccessor.rename(os.path.join(newServerRoot, "Documents"),
-                            newDocumentRoot)
-                    else:
-                        diskAccessor.mkdir(newDocumentRoot)
-            else:
-                if not diskAccessor.exists(newServerRoot):
-                    log("Creating new calendar server root: %s" % (newServerRoot,))
-                    diskAccessor.mkdir(newServerRoot)
-                newDataRoot = os.path.join(newServerRoot, "Data")
-                if not diskAccessor.exists(newDataRoot):
-                    log("Creating new data root: %s" % (newDataRoot,))
-                    diskAccessor.mkdir(newDataRoot)
-                newDocumentRoot = os.path.join(newDataRoot, "Documents")
-                if not diskAccessor.exists(newDocumentRoot):
-                    log("Creating new document root: %s" % (newDocumentRoot,))
-                    diskAccessor.mkdir(newDocumentRoot)
-
-
-    else: # 10.8 -> 10.8
-
-        if oldServerRootValue:
-            if oldServerRootValue.rstrip("/").startswith("/Volumes/"): # external volume
-                log("Using external calendar server root: %s" % (oldServerRootValue,))
-            elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldServerRootValue)):
-                log("Copying calendar server root: %s" % (newServerRoot,))
-                diskAccessor.ditto(
-                    absolutePathWithRoot(sourceRoot, oldServerRootValue),
-                    newServerRoot
-                )
-            else:
-                log("Creating new calendar server root: %s" % (newServerRoot,))
-                diskAccessor.mkdir(newServerRoot)
-                newDataRoot = os.path.join(newServerRoot, "Data")
-                diskAccessor.mkdir(newDataRoot)
-                newDocumentRoot = os.path.join(newDataRoot, "Documents")
-                diskAccessor.mkdir(newDocumentRoot)
-
-    if not diskAccessor.exists(newServerRoot):
-        diskAccessor.mkdir(newServerRoot)
-    diskAccessor.chown(newServerRoot, uid, gid, recursive=True)
-
-    newServerRootValue, newDataRootValue = relativize(newServerRootValue,
-        newDataRootValue)
-    newDataRootValue, newDocumentRootValue = relativize(newDataRootValue,
-        newDocumentRootValue)
-
-
-    return (
-        newServerRoot,
-        newServerRootValue,
-        newDataRootValue
-    )
-
-
-def triggerResourceMigration(newServerRoot):
-    """
-    Leave a file in the server root to act as a signal that the server
-    should migrate locations and resources from OD when it starts up.
-    """
-    triggerPath = os.path.join(newServerRoot, RESOURCE_MIGRATION_TRIGGER)
-    if not os.path.exists(newServerRoot):
-        log("New server root directory doesn't exist: %s" % (newServerRoot,))
-        return
-
-    if not os.path.exists(triggerPath):
-        # Create an empty trigger file
-        log("Creating resource migration trigger file: %s" % (triggerPath,))
-        open(triggerPath, "w").close()
-
-
-def relativize(parent, child):
-    """
-    If child is really a child of parent, make child relative to parent.
-    """
-    if child.startswith(parent):
-        parent = parent.rstrip("/")
-        child = child[len(parent):].strip("/")
-    return parent.rstrip("/"), child.rstrip("/")
-
-
-def absolutePathWithRoot(root, path):
-    """
-    Combine root and path as long as path does not start with /Volumes/
-    """
-    if path.startswith("/Volumes/"):
-        return path
-    else:
-        path = path.strip("/")
-        return os.path.join(root, path)
-
-
-def nextAvailable(path, ext, diskAccessor=None):
-    """
-    If path.ext doesn't exist, return path.ext.  Otherwise return the first path name
-    following the path.N.ext pattern that doesn't exist, where N starts at 1
-    and increments until a non-existent path name is determined.
-
-    @param path: path to examine
-    @type path: C{str}
-    @param ext: filename extension to append (don't include ".")
-    @type ext: C{str}
-    @returns: non-existent path name
-    @rtype: C{str}
-    """
-
-    if diskAccessor is None:
-        diskAccessor = DiskAccessor()
-
-    newPath = "%s.%s" % (path, ext)
-    if not diskAccessor.exists(newPath):
-        return newPath
-
-    i = 1
-    while(True):
-        newPath = "%s.%d.%s" % (path, i, ext)
-        if not diskAccessor.exists(newPath):
-            return newPath
-        i += 1
-
-
-class DiskAccessor(object):
-    """
-    A wrapper around various disk access methods so that unit tests can easily
-    replace these with a stub that doesn't actually require disk access.
-    """
-
-    def exists(self, path):
-        return os.path.exists(path)
-
-    def readFile(self, path):
-        input = file(path)
-        contents = input.read()
-        input.close()
-        return contents
-
-    def mkdir(self, path):
-        if not self.exists(path):
-            return os.mkdir(path)
-        else:
-            return
-
-    def rename(self, before, after):
-        log("Renaming: %s to %s" % (before, after))
-        try:
-            return os.rename(before, after)
-        except OSError:
-            # Can't rename because it's cross-volume; must copy/delete
-            self.ditto(before, after)
-            return os.remove(before)
-
-    def isfile(self, path):
-        return os.path.isfile(path)
-
-    def symlink(self, orig, link):
-        return os.symlink(orig, link)
-
-    def chown(self, path, uid, gid, recursive=False):
-        os.chown(path, uid, gid)
-        if recursive:
-            for root, dirs, files in os.walk(path, followlinks=True):
-                for name in dirs:
-                    os.chown(os.path.join(root, name), uid, gid)
-                for name in files:
-                    os.chown(os.path.join(root, name), uid, gid)
-
-
-    def walk(self, path, followlinks=True):
-        return os.walk(path, followlinks=followlinks)
-
-    def listdir(self, path):
-        return list(os.listdir(path))
-
-    def ditto(self, src, dest):
-        log("Copying with ditto: %s to %s" % (src, dest))
-        return subprocess.call([DITTO, src, dest])
-
-
-if __name__ == '__main__':
-    main()

Deleted: CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarpromotion.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarpromotion.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/contrib/migration/calendarpromotion.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-#
-# PromotionExtra script for calendar server.
-#
-# Copyright (c) 2011-2013 Apple Inc.  All Rights Reserved.
-#
-# IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of.  You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import os
-import shutil
-from pwd import getpwnam
-from grp import getgrnam
-from plistlib import readPlist, writePlist
-
-SRC_CONFIG_DIR = "/Applications/Server.app/Contents/ServerRoot/private/etc/caldavd"
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-DEST_CONFIG_DIR = "%s/Config" % (CALENDAR_SERVER_ROOT,)
-DEST_DATA_DIR = "%s/Data" % (CALENDAR_SERVER_ROOT,)
-CALDAVD_PLIST = "caldavd.plist"
-USER_NAME = "calendar"
-GROUP_NAME = "calendar"
-LOG_DIR = "/var/log/caldavd"
-RUN_DIR = "/var/run/caldavd"
-
-
-def updatePlist(plistData):
-    """
-    Update the passed-in plist data with new values for disabling the XMPPNotifier,
-    to set DBType to empty string indicating we'll be starting our own Postgres server,
-    and to specify the new location for ConfigRoot ("Config" directory beneath ServerRoot).
-
-    @param plistData: the plist data to update in place
-    @type plistData: C{dict}
-    """
-    try:
-        if plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"]:
-            plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"] = False
-    except KeyError:
-        pass
-    plistData["DBType"] = ""
-    plistData["DSN"] = ""
-    plistData["ConfigRoot"] = "Config"
-    plistData["DBImportFile"] = "/Library/Server/Calendar and Contacts/DataDump.sql"
-    # Remove RunRoot and PIDFile keys so they use the new defaults
-    try:
-        del plistData["RunRoot"]
-    except:
-        pass
-    try:
-        del plistData["PIDFile"]
-    except:
-        pass
-
-
-
-def main():
-
-    for dirName in (
-        CALENDAR_SERVER_ROOT,
-        DEST_CONFIG_DIR,
-        DEST_DATA_DIR,
-        LOG_DIR,
-        RUN_DIR
-    ):
-        try:
-            os.mkdir(dirName)
-        except OSError:
-            # Already exists
-            pass
-
-        try:
-            uid = getpwnam(USER_NAME).pw_uid
-            gid = getgrnam(GROUP_NAME).gr_gid
-            os.chown(dirName, uid, gid)
-        except Exception, e:
-            print("Unable to chown %s: %s" % (dirName, e))
-
-
-    plistPath = os.path.join(DEST_CONFIG_DIR, CALDAVD_PLIST)
-
-    if os.path.exists(plistPath):
-        try:
-            plistData = readPlist(plistPath)
-            updatePlist(plistData)
-            writePlist(plistData, plistPath)
-
-        except Exception, e:
-            print("Unable to disable update values in %s: %s" % (plistPath, e))
-
-    else:
-        # Copy configuration
-        srcPlistPath = os.path.join(SRC_CONFIG_DIR, CALDAVD_PLIST)
-        shutil.copy(srcPlistPath, DEST_CONFIG_DIR)
-
-
-if __name__ == '__main__':
-    main()

Deleted: CalendarServer/branches/users/gaya/directorybacker/doc/RFC/draft-daboo-srv-caldav.txt
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/doc/RFC/draft-daboo-srv-caldav.txt	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/doc/RFC/draft-daboo-srv-caldav.txt	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1,840 +0,0 @@
-
-
-
-Network Working Group                                           C. Daboo
-Internet-Draft                                                Apple Inc.
-Updates: 4791,CardDAV-RFC-to-be                       September 16, 2010
-(if approved)
-Intended status: Standards Track
-Expires: March 20, 2011
-
-
-                  Locating CalDAV and CardDAV services
-                       draft-daboo-srv-caldav-10
-
-Abstract
-
-   This specification describes how DNS SRV records, DNS TXT records and
-   well-known URIs can be used together or separately to locate
-   Calendaring Extensions to WebDAV (CalDAV) or vCard Extensions to
-   WebDAV (CardDAV) services.
-
-Status of This Memo
-
-   This Internet-Draft is submitted in full conformance with the
-   provisions of BCP 78 and BCP 79.
-
-   Internet-Drafts are working documents of the Internet Engineering
-   Task Force (IETF).  Note that other groups may also distribute
-   working documents as Internet-Drafts.  The list of current Internet-
-   Drafts is at http://datatracker.ietf.org/drafts/current/.
-
-   Internet-Drafts are draft documents valid for a maximum of six months
-   and may be updated, replaced, or obsoleted by other documents at any
-   time.  It is inappropriate to use Internet-Drafts as reference
-   material or to cite them other than as "work in progress."
-
-   This Internet-Draft will expire on March 20, 2011.
-
-Copyright Notice
-
-   Copyright (c) 2010 IETF Trust and the persons identified as the
-   document authors.  All rights reserved.
-
-   This document is subject to BCP 78 and the IETF Trust's Legal
-   Provisions Relating to IETF Documents
-   (http://trustee.ietf.org/license-info) in effect on the date of
-   publication of this document.  Please review these documents
-   carefully, as they describe your rights and restrictions with respect
-   to this document.  Code Components extracted from this document must
-   include Simplified BSD License text as described in Section 4.e of
-   the Trust Legal Provisions and are provided without warranty as
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 1]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   described in the Simplified BSD License.
-
-Table of Contents
-
-   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
-   2.  Conventions Used in This Document  . . . . . . . . . . . . . .  4
-   3.  CalDAV SRV Service Labels  . . . . . . . . . . . . . . . . . .  4
-   4.  CalDAV and CardDAV Service TXT Records . . . . . . . . . . . .  4
-   5.  CalDAV and CardDAV Service Well-Known URI  . . . . . . . . . .  5
-     5.1.  Example: well-known URI redirects to actual context
-           path . . . . . . . . . . . . . . . . . . . . . . . . . . .  5
-   6.  Client "Bootstrapping" Procedures  . . . . . . . . . . . . . .  5
-   7.  Guidance for Service Providers . . . . . . . . . . . . . . . .  8
-   8.  Security Considerations  . . . . . . . . . . . . . . . . . . .  9
-   9.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . .  9
-     9.1.  caldav Well-Known URI Registration . . . . . . . . . . . . 10
-     9.2.  carddav Well-Known URI Registration  . . . . . . . . . . . 10
-     9.3.  SRV Service Label Registration . . . . . . . . . . . . . . 10
-   10. Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 10
-   11. References . . . . . . . . . . . . . . . . . . . . . . . . . . 10
-     11.1. Normative References . . . . . . . . . . . . . . . . . . . 10
-     11.2. Informative References . . . . . . . . . . . . . . . . . . 12
-   Appendix A.  Change History (to be removed prior to
-                publication as an RFC)  . . . . . . . . . . . . . . . 13
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 2]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-1.  Introduction
-
-   [RFC4791] defines the CalDAV calendar access protocol, based on HTTP
-   [RFC2616], for accessing calendar data stored on a server.  CalDAV
-   clients need to be able to discover appropriate CalDAV servers within
-   their local area network and at other domains, e.g., to minimize the
-   need for end users to know specific details such as the fully
-   qualified domain name (FQDN) and port number for their servers.
-
-   [I-D.ietf-vcarddav-carddav] defines the CardDAV address book access
-   protocol based on HTTP [RFC2616], for accessing contact data stored
-   on a server.  As with CalDAV, clients also need to be able to
-   discover CardDAV servers.
-
-   [RFC2782] defines a DNS-based service discovery protocol that has
-   been widely adopted as a means of locating particular services within
-   a local area network and beyond, using DNS SRV Resource Records
-   (RRs).  This has been enhanced to provide additional service meta-
-   data by use of DNS TXT Resource Records as per
-   [I-D.cheshire-dnsext-dns-sd].
-
-   This specification defines new SRV service types for the CalDAV
-   protocol, and gives an example of how clients can use this together
-   with other protocol features to enable simple client configuration.
-   SRV service types for CardDAV are already defined in Section 11 of
-   [I-D.ietf-vcarddav-carddav].
-
-   Another issue with CalDAV or CardDAV service discovery is that the
-   service might not be located at the "root" URI of the HTTP server
-   hosting it.  Thus a client needs to be able to determine the complete
-   path component of the Request-URI to use in HTTP requests: the
-   "context path".  For example, if CalDAV is implemented as a "servlet"
-   in a web server "container", the servlet "context path" might be
-   "/caldav/".  So the URI for the CalDAV service would be, e.g.,
-   "http://caldav.example.com/caldav/" rather than
-   "http://caldav.example.com/".  SRV RRs by themselves only provide a
-   FQDN and port number for the service, not a path.  Since the client
-   "bootstrapping" process requires initial access to the "context path"
-   of the service, there needs to be a simple way for clients to also
-   discover what that path is.
-
-   This specification makes use of the "well known URI" feature
-   [RFC5785] of HTTP servers to provide a well known URI for CalDAV or
-   CardDAV services that clients can make use of.  The well known URI
-   will point to a resource on the server that is simply a "stub"
-   resource that provides a redirect to the actual "context path"
-   resource representing the service endpoint.
-
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 3]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-2.  Conventions Used in This Document
-
-   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
-   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
-   document are to be interpreted as described in [RFC2119].
-
-3.  CalDAV SRV Service Labels
-
-   This specification adds two SRV service labels for use with CalDAV:
-
-   _caldav:  Identifies a CalDAV server that uses HTTP without transport
-      layer security ([RFC2818]).
-
-   _caldavs:  Identifies a CalDAV server that uses HTTP with transport
-      layer security ([RFC2818]).
-
-   Clients MUST honor "Priority" and "Weight" values in the SRV RRs, as
-   described by [RFC2782].
-
-   Example: service record for server without transport layer security
-
-       _caldav._tcp     SRV 0 1 80 calendar.example.com.
-
-   Example: service record for server with transport layer security
-
-       _caldavs._tcp    SRV 0 1 443 calendar.example.com.
-
-4.  CalDAV and CardDAV Service TXT Records
-
-   When SRV RRs are used to advertise CalDAV and CardDAV services, it is
-   also convenient to be able to specify a "context path" in the DNS to
-   be retrieved at the same time.  To enable that, this specification
-   uses a TXT RR that follows the syntax defined in Section 6 of
-   [I-D.cheshire-dnsext-dns-sd] and defines a "path" key for use in that
-   record.  The value of the key MUST be the actual "context path" to
-   the corresponding service on the server.
-
-   A site might provide TXT records in addition to SRV records for each
-   service.  When present, clients MUST use the "path" value as the
-   "context path" for the service in HTTP requests.  When not present,
-   clients use the ".well-known" URI approach described next.
-
-   Example: text record for service with transport layer security
-
-       _caldavs._tcp    TXT path=/caldav
-
-
-
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 4]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-5.  CalDAV and CardDAV Service Well-Known URI
-
-   Two ".well-known" URIs are registered by this specification for
-   CalDAV and CardDAV services, "caldav" and "carddav" respectively (see
-   Section 9).  These URIs point to a resource that the client can use
-   as the initial "context path" for the service they are trying to
-   connect to.  The server MUST redirect HTTP requests for that resource
-   to the actual "context path" using one of the available mechanisms
-   provided by HTTP (e.g., using a 301, 303, 307 response).  Clients
-   MUST handle HTTP redirects on the ".well-known" URI.  Servers MUST
-   NOT locate the actual CalDAV or CardDAV service endpoint at the
-   ".well-known" URI as per Section 1.1 of [RFC5785].
-
-   Servers SHOULD set an appropriate Cache-Control header value (as per
-   Section 14.9 of [RFC2616]) in the redirect response to ensure caching
-   occurs or does not occur as needed, or as required by the type of
-   response generated.  For example, if it is anticipated that the
-   location of the redirect might change over time, then a "no-cache"
-   value would be used.
-
-   To facilitate "context path's" that might differ from user to user,
-   the server MAY require authentication when a client tries to access
-   the ".well-known" URI (i.e., the server would return a 401 status
-   response to the unauthenticated request from the client, then return
-   the redirect response only after a successful authentication by the
-   client).
-
-5.1.  Example: well-known URI redirects to actual context path
-
-   A CalDAV server has a "context path" that is "/servlet/caldav".  The
-   client will use "/.well-known/caldav" as the path for its
-   "bootstrapping" process after it has first found the FQDN and port
-   number via an SRV lookup or via manual entry of information by the
-   user which the client can parse suitable information from.  When the
-   client makes an HTTP request against "/.well-known/caldav", the
-   server would issue an HTTP redirect response with a Location response
-   header using the path "/servlet/caldav".  The client would then
-   "follow" this redirect to the new resource and continue making HTTP
-   requests there to complete its "bootstrapping" process.
-
-6.  Client "Bootstrapping" Procedures
-
-   This section describes a procedure that CalDAV or CardDAV clients
-   SHOULD use to do their initial configuration based on minimal user
-   input.  The goal is to determine an http: or https: URI that
-   describes the full path to the user's principal-URL [RFC3744].
-
-
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 5]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   1.  Processing user input:
-
-       *  For a CalDAV server:
-
-          +  Minimal input from a user would consist of a calendar user
-             address and a password.  A calendar user address is defined
-             by iCalendar [RFC5545] to be a URI [RFC3986].  Provided a
-             user identifier and a domain name can be extracted from the
-             URI, this simple "bootstrap" configuration can be done.
-
-          +  If the calendar user address is a "mailto:" [RFC2368] URI,
-             the "mailbox" portion of the URI is examined and the
-             "local-part" and "domain" portions extracted.
-
-          +  If the calendar user address is an "http:" [RFC2616] or
-             "https:" [RFC2818] URI, the "userinfo" and "host" portion
-             of the URI [RFC3986] is extracted.
-
-       *  For a CardDAV server:
-
-          +  Minimal input from a user would consist of their email
-             address [RFC5322] for the domain where the CardDAV service
-             is hosted, and a password.  The "mailbox" portion of the
-             email address is examined and the "local-part" and "domain"
-             portions extracted.
-
-   2.  Determination of service FQDN and port number:
-
-       *  An SRV lookup for _caldavs._tcp (for CalDAV) or _carddavs._tcp
-          (for CardDAV) is done with the extracted "domain" as the
-          service domain.
-
-       *  If no result is found, the client can try _caldav._tcp (for
-          CalDAV) or _carddav._tcp (for CardDAV) provided non-SSL
-          connections are appropriate.
-
-       *  If an SRV record is returned, the client extracts the target
-          FQDN and port number.  In the case of multiple SRV records
-          returned, the client MUST use the priority and weight fields
-          in the record to determine which one to pick (as per
-          [RFC2782]).
-
-       *  If an SRV record is not found, the client will need to prompt
-          the user to enter the FQDN and port number information
-          directly, or use some other heuristic, for example using the
-          extracted "domain" as the FQDN and default HTTPS or HTTP port
-          numbers.  In this situation clients MUST first attempt an HTTP
-          connection with transport layer security.
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 6]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   3.  Determination of initial "context path":
-
-       *  When an SRV lookup is done and a valid SRV record returned,
-          the client MUST also query for a corresponding TXT record and
-          check for the presence of a "path" key in its response.  If
-          present, the value of the "path" key is used for the initial
-          "context path".
-
-       *  When an initial "context path" has not been determined from a
-          TXT record, the initial "context path" is taken to be "/.well-
-          known/caldav" (for CalDAV) or "/.well-known/carddav" (for
-          CardDAV).
-
-       *  If the initial "context path" derived from a TXT record
-          generates HTTP errors when targeted by requests, the client
-          SHOULD repeat its bootstrap procedure using the appropriate
-          ".well-known" URI instead.
-
-   4.  Determination of user identifier:
-
-       *  The client will need to make authenticated HTTP requests to
-          the service.  Typically a "user identifier" is required for
-          some form of user/password authentication.  When a user
-          identifier is required, clients MUST first use the "mailbox"
-          portion of the calendar user address provided by the user in
-          the case of a "mailto:" address, and if that results in an
-          authentication failure, SHOULD fall back to using the "local-
-          part" extracted from the "mailto:" address.  For an "http:" or
-          "https:" calendar user address, the "userinfo" portion is used
-          as the user identifier for authentication.  This is in line
-          with the guidance outlined in Section 7.  If these user
-          identifiers result in authentication failure, the client
-          SHOULD prompt the user for a valid identifier.
-
-   5.  Connecting to the service:
-
-       *  Subsequent to configuration, the client will make HTTP
-          requests to the service.  When using "_caldavs" or "_carddavs"
-          services, a transport layer security negotiation is done
-          immediately upon connection.  The client MUST do certificate
-          verification using the procedure outlined in Section 4 of
-          [I-D.saintandre-tls-server-id-check] in regard to verification
-          with an SRV RR as the starting point.
-
-       *  The client does a "PROPFIND" [RFC4918] request with the
-          request URI set to the initial "context path".  The body of
-          the request SHOULD include the DAV:current-user-principal
-          [RFC5397] property as one of the properties to return.  Note
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 7]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-          that clients MUST properly handle HTTP redirect responses for
-          the request.  The server will use the HTTP authentication
-          procedure outlined in [RFC2617] or use some other appropriate
-          authentication schemes to authenticate the user.
-
-       *  If the server returns a 404 Not Found HTTP status response to
-          the request on the initial "context path", clients MAY try
-          repeating the request on the "root" URI "/" or prompt the user
-          for a suitable path.
-
-       *  If the DAV:current-user-principal property is returned on the
-          request, the client uses that value for the principal-URL of
-          the authenticated user.  With that, it can execute a
-          "PROPFIND" request on the principal-URL and discover
-          additional properties for configuration (e.g., calendar or
-          address book "home" collections).
-
-       *  If the DAV:current-user-principal property is not returned,
-          then the client will need to request the principal-URL path
-          from the user in order to continue with configuration.
-
-   Once a successful account discovery step has been done, clients
-   SHOULD cache the service details that were successfully used (user
-   identity, principal-URL with full scheme/host/port details), and re-
-   use those when connecting again at a later time.
-
-   If a subsequent connection attempt fails, or authentication fails
-   persistently, clients SHOULD re-try the SRV lookup and account
-   discovery to "refresh" the cached data.
-
-7.  Guidance for Service Providers
-
-   Service providers wanting to offer CalDAV or CardDAV services that
-   can be configured by clients using SRV records need to follow certain
-   procedures to ensure proper operation.
-
-   o  CalDAV or CardDAV servers SHOULD be configured to allow
-      authentication with calendar user addresses (just taking the
-      "mailbox" portion of any "mailto:" URI) or email addresses
-      respectively, or "user identifiers" extracted from them.  In the
-      former case, the addresses MUST NOT conflict with other forms of
-      permitted user login name.  In the latter case, the extracted
-      "user identifiers" need to be unique across the server and MUST
-      NOT conflict with any login name on the server.
-
-   o  Servers MUST force authentication for "PROPFIND" requests that
-      retrieve the DAV:current-user-principal property to ensure that
-      the value of the DAV:current-user-principal property returned
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 8]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-      corresponds to the principal-URL of the user making the request.
-
-   o  If the service provider uses transport layer security, the service
-      provider MUST ensure a certificate is installed that can be
-      verified by clients using the procedure outlined in Section 4 of
-      [I-D.saintandre-tls-server-id-check] in regard to verification
-      with an SRV RR as the starting point.
-
-   o  Install the appropriate SRV records for the offered services.
-      Optionally include TXT records.
-
-8.  Security Considerations
-
-   Clients that support transport layer security as defined by [RFC2818]
-   SHOULD try the "_caldavs" or "_carddavs" services first before trying
-   the "_caldav" or "_carddav" services respectively.  If a user has
-   explicitly requested a connection with transport layer security, the
-   client MUST NOT use any service information returned for the
-   "_caldav" or "_carddav" services.  Clients MUST follow the
-   certificate verification process specified in
-   [I-D.saintandre-tls-server-id-check].
-
-   A malicious attacker with access to the DNS server data, or able to
-   get spoofed answers cached in a recursive resolver, can potentially
-   cause clients to connect to any server chosen by the attacker.  In
-   the absence of a secure DNS option, clients SHOULD check that the
-   target FQDN returned in the SRV record matches the original service
-   domain that was queried.  If the target FQDN is not in the queried
-   domain, clients SHOULD verify with the user that the SRV target FQDN
-   is suitable for use before executing any connections to the host.
-   Alternatively, if transport layer security is being used for the
-   service, clients MUST use the procedure outlined in Section 4 of
-   [I-D.saintandre-tls-server-id-check] to verify the service.
-
-   Implementations of TLS [RFC5246], used as the basis for transport
-   layer security ([RFC2818]), typically support multiple versions of
-   the protocol as well as the older Secure Sockets Layer (SSL)
-   protocol.  Because of known security vulnerabilities, clients and
-   servers MUST NOT request, offer, or use SSL 2.0.  See Appendix E.2 of
-   [RFC5246] for further details.
-
-9.  IANA Considerations
-
-   This document defines two ".well-known" URIs using the registration
-   procedure and template from Section 5.1 of [RFC5785].
-
-
-
-
-
-
-Daboo                    Expires March 20, 2011                 [Page 9]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-9.1.  caldav Well-Known URI Registration
-
-   URI suffix:  caldav
-
-   Change controller:  IETF.
-
-   Specification document(s):  This RFC.
-
-   Related information:  See also [RFC4791].
-
-9.2.  carddav Well-Known URI Registration
-
-   URI suffix:  carddav
-
-   Change controller:  IETF.
-
-   Specification document(s):  This RFC.
-
-   Related information:  See also [I-D.ietf-vcarddav-carddav].
-
-9.3.  SRV Service Label Registration
-
-   Service labels have been registered according to
-   <http://www.dns-sd.org/ServiceTypes.html> [1] and will be
-   incorporated into IANA once a new registry is available there.
-
-10.  Acknowledgments
-
-   This specification was suggested by discussion that took place within
-   the Calendaring and Scheduling Consortium's CalDAV Technical
-   Committee.  The author thanks the following for their contributions:
-   Stuart Cheshire, Bernard Desruisseaux, Eran Hammer-Lahav, Helge Hess,
-   Arnaud Quillaud, Wilfredo Sanchez, and Joe Touch.
-
-11.  References
-
-11.1.  Normative References
-
-   [I-D.cheshire-dnsext-dns-sd]          Cheshire, S. and M. Krochmal,
-                                         "DNS-Based Service Discovery",
-                                         draft-cheshire-dnsext-dns-sd-06
-                                         (work in progress), March 2010.
-
-   [I-D.ietf-vcarddav-carddav]           Daboo, C., "vCard Extensions to
-                                         WebDAV (CardDAV)",
-                                         draft-ietf-vcarddav-carddav-10
-                                         (work in progress),
-                                         November 2009.
-
-
-
-Daboo                    Expires March 20, 2011                [Page 10]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   [I-D.saintandre-tls-server-id-check]  Saint-Andre, P. and J. Hodges,
-                                         "Representation and
-                                         Verification of Domain-Based
-                                         Application Service Identity in
-                                         Certificates Used with
-                                         Transport Layer Security", draf
-                                         t-saintandre-tls-server-id-
-                                         check-09 (work in progress),
-                                         August 2010.
-
-   [RFC2119]                             Bradner, S., "Key words for use
-                                         in RFCs to Indicate Requirement
-                                         Levels", BCP 14, RFC 2119,
-                                         March 1997.
-
-   [RFC2368]                             Hoffman, P., Masinter, L., and
-                                         J. Zawinski, "The mailto URL
-                                         scheme", RFC 2368, July 1998.
-
-   [RFC2616]                             Fielding, R., Gettys, J.,
-                                         Mogul, J., Frystyk, H.,
-                                         Masinter, L., Leach, P., and T.
-                                         Berners-Lee, "Hypertext
-                                         Transfer Protocol -- HTTP/1.1",
-                                         RFC 2616, June 1999.
-
-   [RFC2617]                             Franks, J., Hallam-Baker, P.,
-                                         Hostetler, J., Lawrence, S.,
-                                         Leach, P., Luotonen, A., and L.
-                                         Stewart, "HTTP Authentication:
-                                         Basic and Digest Access
-                                         Authentication", RFC 2617,
-                                         June 1999.
-
-   [RFC2782]                             Gulbrandsen, A., Vixie, P., and
-                                         L. Esibov, "A DNS RR for
-                                         specifying the location of
-                                         services (DNS SRV)", RFC 2782,
-                                         February 2000.
-
-   [RFC2818]                             Rescorla, E., "HTTP Over TLS",
-                                         RFC 2818, May 2000.
-
-   [RFC3744]                             Clemm, G., Reschke, J., Sedlar,
-                                         E., and J. Whitehead, "Web
-                                         Distributed Authoring and
-                                         Versioning (WebDAV)
-                                         Access Control Protocol",
-
-
-
-Daboo                    Expires March 20, 2011                [Page 11]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-                                         RFC 3744, May 2004.
-
-   [RFC3986]                             Berners-Lee, T., Fielding, R.,
-                                         and L. Masinter, "Uniform
-                                         Resource Identifier (URI):
-                                         Generic Syntax", STD 66,
-                                         RFC 3986, January 2005.
-
-   [RFC4791]                             Daboo, C., Desruisseaux, B.,
-                                         and L. Dusseault, "Calendaring
-                                         Extensions to WebDAV (CalDAV)",
-                                         RFC 4791, March 2007.
-
-   [RFC4918]                             Dusseault, L., "HTTP Extensions
-                                         for Web Distributed Authoring
-                                         and Versioning (WebDAV)",
-                                         RFC 4918, June 2007.
-
-   [RFC5246]                             Dierks, T. and E. Rescorla,
-                                         "The Transport Layer Security
-                                         (TLS) Protocol Version 1.2",
-                                         RFC 5246, August 2008.
-
-   [RFC5322]                             Resnick, P., Ed., "Internet
-                                         Message Format", RFC 5322,
-                                         October 2008.
-
-   [RFC5397]                             Sanchez, W. and C. Daboo,
-                                         "WebDAV Current Principal
-                                         Extension", RFC 5397,
-                                         December 2008.
-
-   [RFC5785]                             Nottingham, M. and E. Hammer-
-                                         Lahav, "Defining Well-Known
-                                         Uniform Resource Identifiers
-                                         (URIs)", RFC 5785, April 2010.
-
-11.2.  Informative References
-
-   [RFC5545]                             Desruisseaux, B., "Internet
-                                         Calendaring and Scheduling Core
-                                         Object Specification
-                                         (iCalendar)", RFC 5545,
-                                         September 2009.
-
-URIs
-
-   [1]  <http://www.dns-sd.org/ServiceTypes.html>
-
-
-
-Daboo                    Expires March 20, 2011                [Page 12]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-Appendix A.  Change History (to be removed prior to publication as an
-             RFC)
-
-   Changes in -09:
-
-   1.  IESG Review: minor editorial changes.
-
-   2.  GenART Review: minor editorial changes.
-
-   3.  GenART Review: "guideline" -> "procedure".
-
-   4.  GenART Review: "port" -> "port number".
-
-   5.  GenART Review: added definition of "context path".
-
-   6.  GenART Review: clarified OPTIONAL nature of suggested client
-       procedure.
-
-   7.  GenART Review: clarified that TXT lookup is an additional query.
-
-   8.  IESG Review: now allow any HTTP redirect response, not just 301.
-
-   9.  IESG Review: added text on cache interaction with redirect.
-
-   Changes in -10:
-
-   1.  AD Review: make client procedure a SHOULD.
-
-   Changes in -08:
-
-   1.  Clarify that email address is a valid input in Section 7 for
-       CardDAV.
-
-   2.  Clarified aspects of DAV:current-user-principal handling for
-       servers.
-
-   3.  Added additional text to indicate TXT being used in abstract and
-       introduction.
-
-   Changes in -07:
-
-   1.  Add password to required minimal user input
-
-   2.  Section 3 -> Section 4 of server-id check draft.
-
-   Changes in -06:
-
-
-
-
-
-Daboo                    Expires March 20, 2011                [Page 13]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   1.  Last call comments: Revised title, abstract and text to indicate
-       that SRV and .well-known can be done separately.
-
-   2.  Revised IANA section to use dns-sd registry for now.
-
-   3.  Added optional TXT RR with path key for service context path in
-       the DNS
-
-   4.  Re-organized client bootstrap to take account of TXT and to call-
-       out the different "phases" involved via a numbered list.
-
-   Changes in -05:
-
-   1.  AD Review: Added "Updates" for 4791 and CardDAV.
-
-   2.  AD Review: Changed SHOULD to MUST for honoring priority and
-       weight.
-
-   3.  AD Review: Added additional reference to 3986 when talking about
-       userinfo/host portions of the URI.
-
-   4.  AD Review: Changed section reference for tls-server-id-check
-       draft.
-
-   5.  AD Review: Changed should to SHOULD when describing PROPFIND
-       request and made 5397 normative.
-
-   6.  AD Review: Made 3744 and 5322 normative references.
-
-   7.  AD Review: Added IANA SRV registration request.
-
-   Changes in -04:
-
-   1.  Added addition text to client guidelines indicating that clients
-       cache the discovery details and can re-do discovery if
-       connections later fail.
-
-   2.  Changed principal-URI to principal-URL.
-
-   Changes in -03:
-
-   1.  Updated to RFC 5785 reference.
-
-   2.  Added SSL v2 restriction from srv-email document added after IESG
-       review.
-
-   3.  Tweaked client/server guidelines to better match HTTP challenge/
-       response authentication mechanism.
-
-
-
-Daboo                    Expires March 20, 2011                [Page 14]
-
-Internet-Draft          SRV for CalDAV & CardDAV          September 2010
-
-
-   Changes in -02:
-
-   1.  Re-organized introduction.
-
-   2.  Brought terminology into line with srv-email document which has
-       been through last call.
-
-   3.  Brought security section into line with srv-email document which
-       has been through last call.
-
-   Changes in -01:
-
-   1.  Added discovery of CardDAV service.
-
-   2.  Now makes use of well-known URIs for the service "context path".
-
-   3.  Updated to RFC 5545 reference.
-
-   4.  Added reference to certificate verification spec.
-
-Author's Address
-
-   Cyrus Daboo
-   Apple Inc.
-   1 Infinite Loop
-   Cupertino, CA  95014
-   USA
-
-   EMail: cyrus at daboo.name
-   URI:   http://www.apple.com/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Daboo                    Expires March 20, 2011                [Page 15]
-

Modified: CalendarServer/branches/users/gaya/directorybacker/run
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/run	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/run	2013-03-15 21:47:42 UTC (rev 10937)
@@ -190,20 +190,15 @@
 
   # If we've been asked to read a configuration key, just read it and exit.
   if [ -n "${read_key}" ]; then
-    "${caldav}/bin/calendarserver_config" "${read_key}";
+    value="$("${caldav}/bin/calendarserver_config" "${read_key}")";
+    IFS="="; set ${value}; echo "$2"; unset IFS;
     exit $?;
   fi;
 
   if "${kill}" || "${restart}"; then
-    # mimic logic of 'fullServerPath' from twistedcaldav/config.py to find the pid file
     pidfile="$("${caldav}/bin/calendarserver_config" "PIDFile")";
-    serverroot="$("${caldav}/bin/calendarserver_config" "ServerRoot")";
-    runroot="$("${caldav}/bin/calendarserver_config" "RunRoot")";
-    # examine first character of $pidfile
-    if ( [ "${pidfile:0:1}" == "/" ] || [ "${pidfile:0:1}" == "." ]; ) then
-        pidfile=$pidfile;
-        else pidfile=${serverroot}/${runroot}/${pidfile};
-    fi
+    # Split key and value on "=" and just grab the value
+    IFS="="; set ${pidfile}; pidfile="$2"; unset IFS;
     if [ ! -r "${pidfile}" ]; then
       echo "Unreadable PID file: ${pidfile}";
       exit 1

Modified: CalendarServer/branches/users/gaya/directorybacker/setup.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/setup.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/setup.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -132,7 +132,7 @@
                              "bin/calendarserver_backup",
                              "bin/calendarserver_bootstrap_database",
                              "bin/calendarserver_command_gateway",
-                            #"bin/calendarserver_config", # Used by run script.
+                             "bin/calendarserver_config",
                             #"bin/calendarserver_dbinspect",
                             #"bin/calendarserver_dkimtool",
                              "bin/calendarserver_export",

Modified: CalendarServer/branches/users/gaya/directorybacker/support/Makefile.Apple
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/support/Makefile.Apple	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/support/Makefile.Apple	2013-03-15 21:47:42 UTC (rev 10937)
@@ -85,7 +85,7 @@
 	$(_v) cd $(BuildDirectory)/pycrypto-2.5       && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
 	$(_v) for so in $$(find "$(DSTROOT)$(PY_HOME)/lib" -type f -name '*.so'); do $(STRIP) -Sx "$${so}"; done 
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)"
-	$(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-apple.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)/caldavd.plist"
+	$(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-apple.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)/caldavd-apple.plist"
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)"
 	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/com.apple.webapp.contacts.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)/com.apple.webapp.contacts.plist"
 	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/com.apple.webapp.contactsssl.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)/com.apple.webapp.contactsssl.plist"
@@ -112,22 +112,6 @@
 	$(_v) $(INSTALL_DIRECTORY) -o "$(CS_USER)" -g "$(CS_GROUP)" -m 0755 "$(DSTROOT)$(VARDIR)/log$(CALDAVDSUBDIR)"
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(NSLIBRARYDIR)/LaunchDaemons"
 	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/launchd/calendarserver.plist" "$(DSTROOT)$(SIPP)$(NSLIBRARYDIR)/LaunchDaemons/org.calendarserver.calendarserver.plist"
-	@echo "Installing migration extras script..."
-	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/MigrationExtras"
-	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarmigrator.py" "$(DSTROOT)$(SERVERSETUP)/MigrationExtras/70_calendarmigrator.py"
-	$(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/MigrationExtras/70_calendarmigrator.py"
-	@echo "Installing common extras script..."
-	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/CommonExtras"
-	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarcommonextra.py" "$(DSTROOT)$(SERVERSETUP)/CommonExtras/70_calendarcommonextra.py"
-	$(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/CommonExtras/70_calendarcommonextra.py"
-	@echo "Installing server promotion extras script..."
-	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/PromotionExtras"
-	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarpromotion.py" "$(DSTROOT)$(SERVERSETUP)/PromotionExtras/59_calendarpromotion.py"
-	$(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/PromotionExtras/59_calendarpromotion.py"
-	@echo "Installing server uninstall extras script..."
-	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/UninstallExtras"
-	$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendardemotion.py" "$(DSTROOT)$(SERVERSETUP)/UninstallExtras/59_calendardemotion.py"
-	$(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/UninstallExtras/59_calendardemotion.py"
 	@echo "Installing changeip script..."
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(LIBEXECDIR)/changeip"
 	$(_v) $(INSTALL_FILE) "$(Sources)/calendarserver/tools/changeip_calendar.py" "$(DSTROOT)$(SIPP)$(LIBEXECDIR)/changeip/changeip_calendar.py"

Modified: CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/queue.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/queue.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/queue.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1197,8 +1197,6 @@
         return self.choosePerformer(onlyLocally=True).performWork(table, workID)
 
 
-
-
     def allWorkItemTypes(self):
         """
         Load all the L{WorkItem} types that this node can process and return
@@ -1208,7 +1206,14 @@
         """
         # TODO: For completeness, this may need to involve a plugin query to
         # make sure that all WorkItem subclasses are imported first.
-        return WorkItem.__subclasses__()
+        for workItemSubclass in WorkItem.__subclasses__():
+            # TODO: It might be a good idea to offload this table-filtering to
+            # SchemaSyntax.__contains__, adding in some more structure-
+            # comparison of similarly-named tables.  For now a name check is
+            # sufficient.
+            if workItemSubclass.table.model.name in set([x.model.name for x in
+                                                         self.schema]):
+                yield workItemSubclass
 
 
     def totalNumberOfNodes(self):

Modified: CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/test/test_queue.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/test/test_queue.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twext/enterprise/test/test_queue.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -337,7 +337,7 @@
 
 
     @inlineCallbacks
-    def FIXME_test_notBeforeWhenCheckingForLostWork(self):
+    def test_notBeforeWhenCheckingForLostWork(self):
         """
         L{PeerConnectionPool._periodicLostWorkCheck} should execute any
         outstanding work items, but only those that are expired.

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/config.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/config.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -33,7 +33,7 @@
 class ConfigDict(dict):
     """
     Dictionary which can be accessed using attribute syntax, because
-    that reads an writes nicer in code.  For example:
+    that reads and writes nicer in code.  For example:
       C{config.Thingo.Tiny.Tweak}
     instead of:
       C{config["Thingo"]["Tiny"]["Tweak"]}
@@ -211,7 +211,7 @@
         self.reset()
 
     def updateDefaults(self, items):
-        _mergeData(self._provider.getDefaults(), items)
+        mergeData(self._provider.getDefaults(), items)
         self.update(items)
 
     def update(self, items=None, reloading=False):
@@ -225,7 +225,7 @@
         # Call hooks
         for hook in self._preUpdateHooks:
             hook(self._data, items, reloading=reloading)
-        _mergeData(self._data, items)
+        mergeData(self._data, items)
         for hook in self._postUpdateHooks:
             hook(self._data, reloading=reloading)
 
@@ -234,7 +234,7 @@
 
     def load(self, configFile):
         self._provider.setConfigFileName(configFile)
-        configDict = ConfigDict(self._provider.loadConfig())
+        configDict = self._provider.loadConfig()
         if not self._provider.hasErrors():
             self.update(configDict)
         else:
@@ -242,7 +242,7 @@
                                      % (self._provider.getConfigFileName(),))
 
     def reload(self):
-        configDict = ConfigDict(self._provider.loadConfig())
+        configDict = self._provider.loadConfig()
         if not self._provider.hasErrors():
             if self._beforeResetHook:
                 # Give the beforeResetHook a chance to stash away values we want
@@ -263,7 +263,15 @@
         self._data = ConfigDict(copy.deepcopy(self._provider.getDefaults()))
         self._dirty = True
 
-def _mergeData(oldData, newData):
+def mergeData(oldData, newData):
+    """
+    Merge two ConfigDict objects; oldData will be updated with all the keys
+    and values from newData
+    @param oldData: the object to modify
+    @type oldData: ConfigDict
+    @param newData: the object to copy data from
+    @type newData: ConfigDict
+    """
     for key, value in newData.iteritems():
         if isinstance(value, (dict,)):
             if key in oldData:
@@ -271,7 +279,7 @@
                     "%r in %r is not a ConfigDict" % (oldData[key], oldData)
             else:
                 oldData[key] = {}
-            _mergeData(oldData[key], value)
+            mergeData(oldData[key], value)
         else:
             oldData[key] = value
 

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/appleopendirectory.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/appleopendirectory.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -56,17 +56,22 @@
         return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, self.node)
 
 
-    def __init__(self, params):
+    def __init__(self, params, odModule=None):
         """
         @param params: a dictionary containing the following keys:
-            node: an OpenDirectory node name to bind to.
-            restrictEnabledRecords: C{True} if a group in the
-              directory is to be used to determine which calendar
-              users are enabled.
-            restrictToGroup: C{str} guid or name of group used to
-              restrict enabled users.
-            cacheTimeout: C{int} number of minutes before cache is invalidated.
-            negativeCache: C{False} cache the fact that a record wasn't found
+
+            - node: an OpenDirectory node name to bind to.
+
+            - restrictEnabledRecords: C{True} if a group in the directory is to
+              be used to determine which calendar users are enabled.
+
+            - restrictToGroup: C{str} guid or name of group used to restrict
+              enabled users.
+
+            - cacheTimeout: C{int} number of minutes before cache is
+              invalidated.
+
+            - negativeCache: C{False} cache the fact that a record wasn't found
         """
         defaults = {
             'node' : '/Search',
@@ -90,7 +95,9 @@
         super(OpenDirectoryService, self).__init__(params['cacheTimeout'],
                                                    params['negativeCaching'])
 
-        self.odModule = namedModule(config.OpenDirectoryModule)
+        if odModule is None:
+            odModule = namedModule(config.OpenDirectoryModule)
+        self.odModule = odModule
 
         try:
             directory = self.odModule.odInit(params['node'])
@@ -1447,7 +1454,7 @@
                     self.shortNames[0],
                     challenge,
                     response,
-                    credentials.originalMethod if credentials.originalMethod else credentials.method
+                    credentials.method
                 ):
                     try:
                         cache = self.digestcache
@@ -1465,7 +1472,8 @@
     Challenge: %s
     Response:  %s
     Method:    %s
-""" % (self.nodeName, self.shortNames[0], challenge, response, credentials.originalMethod if credentials.originalMethod else credentials.method))
+""" % (self.nodeName, self.shortNames[0], challenge, response,
+       credentials.method))
 
             except self.service.odModule.ODError, e:
                 self.log_error(

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/calendaruserproxy.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/calendaruserproxy.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -375,7 +375,9 @@
             p = self.pcollection.principalForUID(uid)
             if p:
                 # Only principals enabledForLogin can be a delegate
-                if p.record.enabledForLogin:
+                # (and groups as well)
+                if (p.record.enabledForLogin or 
+                    p.record.recordType == p.record.service.recordType_groups):
                     found.append(p)
                 # Make sure any outstanding deletion timer entries for
                 # existing principals are removed

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts-modified.xml
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts-modified.xml	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts-modified.xml	2013-03-15 21:47:42 UTC (rev 10937)
@@ -115,6 +115,35 @@
     <name>佐藤佐藤佐藤</name>
     <email-address>nonascii at example.com</email-address>
   </user>
+  <user>
+    <uid>delegator</uid>
+    <guid>FC465590-E9E9-4746-ACE8-6C756A49FE4D</guid>
+    <password>a</password>
+    <name>Calendar Delegator</name>
+    <email-address>calendardelegator at example.com</email-address>
+  </user>
+  <user>
+    <uid>occasionaldelegate</uid>
+    <guid>EC465590-E9E9-4746-ACE8-6C756A49FE4D</guid>
+    <password>a</password>
+    <name>Occasional Delegate</name>
+    <email-address>occasional at example.com</email-address>
+  </user>
+    <user>
+    <uid>delegateviagroup</uid>
+    <guid>46D9D716-CBEE-490F-907A-66FA6C3767FF</guid>
+    <password>a</password>
+    <name>Delegate Via Group</name>
+    <email-address>delegateviagroup at example.com</email-address>
+  </user>
+  <group>
+    <uid>delegategroup</uid>
+    <guid>00599DAF-3E75-42DD-9DB7-52617E79943F</guid>
+    <name>Delegate Group</name>
+    <members>
+      <member type="users">delegateviagroup</member>
+    </members>
+  </group>
   <user repeat="2">
     <uid>user%02d</uid>
     <guid>user%02d</guid>

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts.xml	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/accounts.xml	2013-03-15 21:47:42 UTC (rev 10937)
@@ -124,6 +124,21 @@
     <name>Occasional Delegate</name>
     <email-address>occasional at example.com</email-address>
   </user>
+    <user>
+    <uid>delegateviagroup</uid>
+    <guid>46D9D716-CBEE-490F-907A-66FA6C3767FF</guid>
+    <password>a</password>
+    <name>Delegate Via Group</name>
+    <email-address>delegateviagroup at example.com</email-address>
+  </user>
+  <group>
+    <uid>delegategroup</uid>
+    <guid>00599DAF-3E75-42DD-9DB7-52617E79943F</guid>
+    <name>Delegate Group</name>
+    <members>
+      <member type="users">delegateviagroup</member>
+    </members>
+  </group>
   <user repeat="2">
     <uid>user%02d</uid>
     <guid>user%02d</guid>

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/augments.xml	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/augments.xml	2013-03-15 21:47:42 UTC (rev 10937)
@@ -186,4 +186,10 @@
     <enable-calendar>true</enable-calendar>
     <enable-login>true</enable-login>
   </record>
+  <record>
+    <uid>00599DAF-3E75-42DD-9DB7-52617E79943F</uid>
+    <enable>true</enable>
+    <enable-calendar>false</enable-calendar>
+    <enable-login>false</enable-login>
+  </record>
 </augments>

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/proxies.xml
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/proxies.xml	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/proxies.xml	2013-03-15 21:47:42 UTC (rev 10937)
@@ -63,6 +63,7 @@
     <guid>FC465590-E9E9-4746-ACE8-6C756A49FE4D</guid>
     <proxies>
       <member>EC465590-E9E9-4746-ACE8-6C756A49FE4D</member>
+      <member>00599DAF-3E75-42DD-9DB7-52617E79943F</member>
     </proxies>
   </record>
 </proxies>

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_directory.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_directory.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_directory.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -220,6 +220,8 @@
         self.assertEquals(
             groups,
             {
+                '00599DAF-3E75-42DD-9DB7-52617E79943F':
+                    set(['46D9D716-CBEE-490F-907A-66FA6C3767FF']),
                 '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1':
                     set(['8B4288F6-CC82-491D-8EF9-642EF4F3E7D0']),
                 'admin':
@@ -250,6 +252,8 @@
         self.assertEquals(
             aliases,
             {
+                '00599DAF-3E75-42DD-9DB7-52617E79943F':
+                    '00599DAF-3E75-42DD-9DB7-52617E79943F',
                 '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1':
                     '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
                  'admin': 'admin',
@@ -286,7 +290,7 @@
 
         # Allow an update by unlocking the cache
         yield cache.releaseLock()
-        self.assertEquals((False, 8, 8), (yield updater.updateCache()))
+        self.assertEquals((False, 9, 9), (yield updater.updateCache()))
 
         # Verify cache is populated:
         self.assertTrue((yield cache.isPopulated()))
@@ -382,7 +386,7 @@
         # that wsanchez is only a proxy for gemini (since that assignment does not involve groups)
         self.directoryService.xmlFile = dirTest.child("accounts-modified.xml")
         self.directoryService._alwaysStat = True
-        self.assertEquals((False, 7, 1), (yield updater.updateCache()))
+        self.assertEquals((False, 8, 1), (yield updater.updateCache()))
         delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, "wsanchez")
         proxyFor = (yield delegate.proxyFor(True))
         self.assertEquals(
@@ -689,8 +693,8 @@
         self.assertFalse((yield cache.isPopulated()))
         fast, numMembers, numChanged = (yield updater.updateCache(fast=True))
         self.assertEquals(fast, False)
-        self.assertEquals(numMembers, 8)
-        self.assertEquals(numChanged, 8)
+        self.assertEquals(numMembers, 9)
+        self.assertEquals(numChanged, 9)
         self.assertTrue(snapshotFile.exists())
         self.assertTrue((yield cache.isPopulated()))
 
@@ -708,7 +712,7 @@
         # Try an update which faults in from the directory (fast=False)
         fast, numMembers, numChanged = (yield updater.updateCache(fast=False))
         self.assertEquals(fast, False)
-        self.assertEquals(numMembers, 8)
+        self.assertEquals(numMembers, 9)
         self.assertEquals(numChanged, 0)
 
         # Verify the snapshot contains the pickled dictionary we expect
@@ -716,6 +720,10 @@
         self.assertEquals(
             members,
             {
+                "46D9D716-CBEE-490F-907A-66FA6C3767FF":
+                    set([
+                        u"00599DAF-3E75-42DD-9DB7-52617E79943F",
+                    ]),
                 "5A985493-EE2C-4665-94CF-4DFEA3A89500":
                     set([
                         u"non_calendar_group",

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_opendirectory.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_opendirectory.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -19,6 +19,7 @@
 except ImportError:
     pass
 else:
+    from collections import defaultdict
     from twisted.trial.unittest import SkipTest
     from twisted.internet.defer import inlineCallbacks
     from twisted.python.runtime import platform
@@ -28,7 +29,24 @@
     from twistedcaldav.directory.directory import DirectoryService
     from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
     from calendarserver.platform.darwin.od import dsattributes
+    from txdav.common.datastore.test.util import deriveValue, withSpecialValue
 
+    class DigestAuthModule(object):
+        """
+        Stand-in for either configurable OD module, that verifies the response
+        according to its '.response' attribute, set by the test.
+        """
+        class ODError(Exception):
+            pass
+
+        def odInit(self, node):
+            return self
+
+        def authenticateUserDigest(self, directory, node, user, challenge,
+                                   response, method):
+            val = (response == self.response)
+            return val
+
     # Wonky hack to prevent unclean reactor shutdowns
     class DummyReactor(object):
         @staticmethod
@@ -60,7 +78,8 @@
                     {
                         "node" : "/Search",
                         "augmentService": augment.AugmentXMLDB(xmlFiles=()),
-                    }
+                    },
+                    odModule=deriveValue(self, "odModule", lambda self: None)
                 )
             except ImportError, e:
                 raise SkipTest("OpenDirectory module is not available: %s" % (e,))
@@ -87,6 +106,8 @@
             )
             self.assertEquals(record.fullName, "")
 
+
+        @withSpecialValue("odModule", DigestAuthModule())
         def test_invalidODDigest(self):
             record = OpenDirectoryRecord(
                 service               = self.service(),
@@ -105,11 +126,16 @@
                 extReadOnlyProxies    = [],
             )
 
-            digestFields = {}
-            digested = DigestedCredentials("user", "GET", "example.com", digestFields, None)
+            digestFields = defaultdict(lambda: "...")
+            digested = DigestedCredentials("user", "GET", "example.com",
+                                           digestFields)
+            od = deriveValue(self, "odModule", lambda x: None)
+            od.response = "invalid"
 
             self.assertFalse(record.verifyCredentials(digested))
 
+
+        @withSpecialValue("odModule", DigestAuthModule())
         def test_validODDigest(self):
             record = OpenDirectoryRecord(
                 service               = self.service(),
@@ -136,8 +162,8 @@
                 "response":"123",
                 "algorithm":"md5",
             }
-
-            response = (
+            od = deriveValue(self, "odModule", lambda self: None)
+            od.response = (
                 'Digest username="%(username)s", '
                 'realm="%(realm)s", '
                 'nonce="%(nonce)s", '
@@ -146,9 +172,8 @@
                 'algorithm=%(algorithm)s'
             ) % digestFields
 
-            record.digestcache = {}
-            record.digestcache["/"] = response
-            digested = DigestedCredentials("user", "GET", "example.com", digestFields, None)
+            digested = DigestedCredentials("user", "GET", "example.com",
+                                           digestFields)
 
             self.assertTrue(record.verifyCredentials(digested))
 
@@ -469,5 +494,6 @@
                     "node" : "/Search",
                     "recordTypes" : (DirectoryService.recordType_users, DirectoryService.recordType_groups),
                     "augmentService" : augment.AugmentXMLDB(xmlFiles=()),
-                }
+                },
+                odModule=deriveValue(self, "odModule", lambda x: None)
             )

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -462,7 +462,17 @@
         """
         self.assertEquals(
             set((yield calendaruserproxy.ProxyDBService.getAllMembers())), #@UndefinedVariable
-            set([u'6423F94A-6B76-4A3A-815B-D52CFD77935D', u'8A985493-EE2C-4665-94CF-4DFEA3A89500', u'9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2', u'both_coasts', u'left_coast', u'non_calendar_group', u'recursive1_coasts', u'recursive2_coasts', u'EC465590-E9E9-4746-ACE8-6C756A49FE4D'])
+            set([
+                u'00599DAF-3E75-42DD-9DB7-52617E79943F',
+                u'6423F94A-6B76-4A3A-815B-D52CFD77935D',
+                u'8A985493-EE2C-4665-94CF-4DFEA3A89500',
+                u'9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2',
+                u'both_coasts',
+                u'left_coast',
+                u'non_calendar_group',
+                u'recursive1_coasts',
+                u'recursive2_coasts',
+                u'EC465590-E9E9-4746-ACE8-6C756A49FE4D'])
         )
 
 
@@ -470,6 +480,7 @@
     def test_hideDisabledDelegates(self):
         """
         Delegates who are not enabledForLogin are "hidden" from the delegate lists
+        (but groups *are* allowed)
         """
 
         record = self.directoryService.recordWithGUID("EC465590-E9E9-4746-ACE8-6C756A49FE4D")
@@ -477,19 +488,19 @@
         record.enabledForLogin = True
         yield self._groupMembersTest(
             DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
-            ("Occasional Delegate",),
+            ("Occasional Delegate", "Delegate Via Group", "Delegate Group"),
         )
 
         # Login disabled -- no longer shown as a delegate
         record.enabledForLogin = False
         yield self._groupMembersTest(
             DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
-            [],
+            ("Delegate Via Group", "Delegate Group"),
         )
 
         # Login re-enabled -- once again a delegate (it wasn't not removed from proxydb)
         record.enabledForLogin = True
         yield self._groupMembersTest(
             DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
-            ("Occasional Delegate",),
+            ("Occasional Delegate", "Delegate Via Group", "Delegate Group"),
         )

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/scheduling/imip/inbound.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/scheduling/imip/inbound.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -21,6 +21,7 @@
 from calendarserver.tap.util import FakeRequest
 import email.utils
 from twext.enterprise.dal.record import fromTable
+from twext.enterprise.dal.syntax import Delete
 from twext.enterprise.queue import WorkItem
 from twext.python.log import Logger, LoggingMixIn
 from twisted.application import service
@@ -78,11 +79,15 @@
 
 class IMIPPollingWork(WorkItem, fromTable(schema.IMIP_POLLING_WORK)):
 
-    # FIXME: delete all other polling work items
     # FIXME: purge all old tokens here
+    group = "imip_polling"
 
     @inlineCallbacks
     def doWork(self):
+
+        # Delete all other work items
+        yield Delete(From=self.table, Where=None).on(self.transaction)
+
         mailRetriever = self.transaction._mailRetriever
         if mailRetriever is not None:
             try:

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/stdconfig.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/stdconfig.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -29,8 +29,8 @@
 from twext.python.log import clearLogLevels, setLogLevelForNamespace
 
 from twistedcaldav import caldavxml, customxml, carddavxml, mkcolxml
-from twistedcaldav.config import ConfigProvider, ConfigurationError
-from twistedcaldav.config import config, _mergeData, fullServerPath
+from twistedcaldav.config import ConfigProvider, ConfigurationError, ConfigDict
+from twistedcaldav.config import config, mergeData, fullServerPath
 from twistedcaldav.util import getPasswordFromKeychain
 from twistedcaldav.util import KeychainAccessError, KeychainPasswordNotFound
 
@@ -41,7 +41,7 @@
 log = Logger()
 
 if platform.isMacOSX():
-    DEFAULT_CONFIG_FILE = "/Library/Server/Calendar and Contacts/Config/caldavd.plist"
+    DEFAULT_CONFIG_FILE = "/Applications/Server.app/Contents/ServerRoot/private/etc/caldavd/caldavd-apple.plist"
 else:
     DEFAULT_CONFIG_FILE = "/etc/caldavd/caldavd.plist"
 
@@ -153,6 +153,7 @@
         },
         "resourceSchema": {
             "resourceInfoAttr": None, # contains location/resource info
+            "autoAcceptGroupAttr": None, # auto accept group
         },
         "partitionSchema": {
             "serverIdAttr": None, # maps to augments server-id
@@ -1010,7 +1011,10 @@
     # America/Los_Angeles.
     "DefaultTimezone" : "",
 
+    # These two aren't relative to ConfigRoot:
     "Includes": [], # Other plists to parse after this one
+    "WritableConfigFile" : "", # which config file calendarserver_config should
+        # write to for changes; empty string means the main config file.
 }
 
 
@@ -1038,15 +1042,19 @@
         configDict = {}
         if self._configFileName:
             configDict = self._parseConfigFromFile(self._configFileName)
+        configDict = ConfigDict(configDict)
         # Now check for Includes and parse and add each of those
         if "Includes" in configDict:
-            configRoot = os.path.join(configDict.ServerRoot, configDict.ConfigRoot)
             for include in configDict.Includes:
-                path = _expandPath(fullServerPath(configRoot, include))
-                additionalDict = self._parseConfigFromFile(path)
-                if additionalDict:
-                    log.info("Adding configuration from file: '%s'" % (path,))
-                    configDict.update(additionalDict)
+                # Includes are not relative to ConfigRoot
+                path = _expandPath(include)
+                if os.path.exists(path):
+                    additionalDict = ConfigDict(self._parseConfigFromFile(path))
+                    if additionalDict:
+                        log.info("Adding configuration from file: '%s'" % (path,))
+                        mergeData(configDict, additionalDict)
+                else:
+                    log.warn("Missing configuration file: '%s'" % (path,))
         return configDict
 
 
@@ -1063,7 +1071,6 @@
         return configDict
 
 
-
 def _expandPath(path):
     if '$' in path:
         return path.replace('$', getfqdn())
@@ -1102,7 +1109,6 @@
     Post-update configuration hook for making all configured paths relative to
     their respective root directories rather than the current working directory.
     """
-
     # Remove possible trailing slash from ServerRoot
     try:
         configDict["ServerRoot"] = configDict["ServerRoot"].rstrip("/")
@@ -1165,7 +1171,7 @@
         if dsType == configDict.DirectoryService.type:
             oldParams = configDict.DirectoryService.params
             newParams = items.DirectoryService.get("params", {})
-            _mergeData(oldParams, newParams)
+            mergeData(oldParams, newParams)
         else:
             if dsType in DEFAULT_SERVICE_PARAMS:
                 configDict.DirectoryService.params = copy.deepcopy(DEFAULT_SERVICE_PARAMS[dsType])
@@ -1195,7 +1201,7 @@
         if dsType == configDict.ResourceService.type:
             oldParams = configDict.ResourceService.params
             newParams = items.ResourceService.get("params", {})
-            _mergeData(oldParams, newParams)
+            mergeData(oldParams, newParams)
         else:
             if dsType in DEFAULT_RESOURCE_PARAMS:
                 configDict.ResourceService.params = copy.deepcopy(DEFAULT_RESOURCE_PARAMS[dsType])
@@ -1227,7 +1233,7 @@
         if dsType == configDict.DirectoryAddressBook.type:
             oldParams = configDict.DirectoryAddressBook.params
             newParams = items["DirectoryAddressBook"].get("params", {})
-            _mergeData(oldParams, newParams)
+            mergeData(oldParams, newParams)
         else:
             if dsType in directoryAddressBookBackingServiceDefaultParams:
                 configDict.DirectoryAddressBook.params = copy.deepcopy(directoryAddressBookBackingServiceDefaultParams[dsType])
@@ -1238,7 +1244,7 @@
         if param not in directoryAddressBookBackingServiceDefaultParams[dsType]:
             raise ConfigurationError("Parameter %s is not supported by service %s" % (param, dsType))
 
-    _mergeData(configDict, items)
+    mergeData(configDict, items)
 
     for param in tuple(configDict.DirectoryAddressBook.params):
         if param not in directoryAddressBookBackingServiceDefaultParams[configDict.DirectoryAddressBook.type]:
@@ -1401,10 +1407,20 @@
             ):
                 if not service[protocol]["Topic"]:
                     certPath = service[protocol]["CertificatePath"]
-                    if certPath and os.path.exists(certPath):
-                        topic = getAPNTopicFromCertificate(certPath)
-                        service[protocol]["Topic"] = topic
+                    if certPath:
+                        if os.path.exists(certPath):
+                            topic = getAPNTopicFromCertificate(certPath)
+                            service[protocol]["Topic"] = topic
+                        else:
+                            log.error("APNS certificate not found: %s" %
+                                (certPath,))
+                    else:
+                        log.error("APNS certificate path not specified")
 
+                if not service[protocol]["Topic"]:
+                    log.error("APNS cannot proceed; disabling APNS")
+                    service["Enabled"] = False
+
                 # If we already have the cert passphrase, don't fetch it again
                 if service[protocol]["Passphrase"]:
                     continue
@@ -1414,13 +1430,13 @@
                 try:
                     passphrase = getPasswordFromKeychain(accountName)
                     service[protocol]["Passphrase"] = passphrase
-                    log.info("%s APN certificate passphrase retreived from keychain" % (protocol,))
+                    log.info("%s APNS certificate passphrase retreived from keychain" % (protocol,))
                 except KeychainAccessError:
                     # The system doesn't support keychain
                     pass
                 except KeychainPasswordNotFound:
                     # The password doesn't exist in the keychain.
-                    log.info("%s APN certificate passphrase not found in keychain" % (protocol,))
+                    log.info("%s APNS certificate passphrase not found in keychain" % (protocol,))
 
 
 

Modified: CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/test/test_config.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/twistedcaldav/test/test_config.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -17,7 +17,7 @@
 from twext.python.plistlib import writePlist #@UnresolvedImport
 from twext.python.log import logLevelForNamespace
 
-from twistedcaldav.config import config, ConfigDict
+from twistedcaldav.config import config, ConfigDict, mergeData
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider,\
     RELATIVE_PATHS
@@ -371,6 +371,40 @@
         configDict._x = "X"
         self.assertEquals(configDict._x, "X")
 
+    def test_mergeData(self):
+        """
+        Verify we don't lose keys which are present in the old but not
+        replaced in the new.
+        """
+        old = ConfigDict({
+            "Scheduling" : ConfigDict({
+                "iMIP" : ConfigDict({
+                    "Enabled" : True,
+                    "Receiving" : ConfigDict({
+                        "Username" : "xyzzy",
+                        "Server" : "example.com",
+                    }),
+                    "Sending" : ConfigDict({
+                        "Username" : "plugh",
+                    }),
+                    "AddressPatterns" : ["mailto:.*"],
+                }),
+            }),
+        })
+        new = ConfigDict({
+            "Scheduling" : ConfigDict({
+                "iMIP" : ConfigDict({
+                    "Enabled" : False,
+                    "Receiving" : ConfigDict({
+                        "Username" : "changed",
+                    }),
+                }),
+            }),
+        })
+        mergeData(old, new)
+        self.assertEquals(old.Scheduling.iMIP.Receiving.Server, "example.com")
+        self.assertEquals(old.Scheduling.iMIP.Sending.Username, "plugh")
+
     def test_SimpleInclude(self):
 
         testConfigMaster = """<?xml version="1.0" encoding="UTF-8"?>

Modified: CalendarServer/branches/users/gaya/directorybacker/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/txdav/caldav/datastore/sql.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/txdav/caldav/datastore/sql.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -1162,7 +1162,7 @@
             # Determine attachment mode (ignore inbox's) - NB we have to do this
             # after setting up other properties as UID at least is needed
             self._attachment = _ATTACHMENTS_MODE_NONE
-            if self._dropboxID is None:
+            if not self._dropboxID:
                 if self._parentCollection.name() != "inbox":
                     if component.hasPropertyInAnyComponent("X-APPLE-DROPBOX"):
                         self._attachment = _ATTACHMENTS_MODE_WRITE
@@ -1582,7 +1582,7 @@
         for managed_id in added:
             changed[managed_id] = newattached[managed_id]
 
-        if self._dropboxID is None:
+        if not self._dropboxID:
             self._dropboxID = str(uuid.uuid4())
         changes = yield self._addingManagedIDs(self._txn, self._parentCollection, self._dropboxID, changed, component.resourceUID())
 
@@ -1714,7 +1714,7 @@
             raise AttachmentStoreFailed
         yield t.loseConnection()
 
-        if self._dropboxID is None:
+        if not self._dropboxID:
             self._dropboxID = str(uuid.uuid4())
         attachment._objectDropboxID = self._dropboxID
 
@@ -2119,7 +2119,7 @@
         @return: C{True} if this attachment exists, C{False} otherwise.
         """
         att = schema.ATTACHMENT
-        if self._dropboxID is not None:
+        if self._dropboxID:
             where = (att.DROPBOX_ID == self._dropboxID).And(
                    att.PATH == self._name)
         else:

Modified: CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql.py	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql.py	2013-03-15 21:47:42 UTC (rev 10937)
@@ -353,7 +353,7 @@
             toFile.write("\n")
             toFile.write("SQL: %s\n" % (sql,))
             toFile.write("Rows: %s\n" % (rows,))
-            toFile.write("Time (ms): %.3f\n" % (t,))
+            toFile.write("Time (ms): %.3f\n" % (t * 1000.0,))
         toFile.write("***\n\n")
 
         if self.logFileName:

Modified: CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_12_to_13.sql
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_12_to_13.sql	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_12_to_13.sql	2013-03-15 21:47:42 UTC (rev 10937)
@@ -22,13 +22,24 @@
 
 create sequence ATTACHMENT_ID_SEQ;
 
+alter table ATTACHMENT
+ drop primary key;
 
+-- not needed; DROPBOX_ID becomes nullable after dropping primary key above
+-- alter table ATTACHMENT
+--  modify (DROPBOX_ID null);
+
+-- We want ATTACHMENT_ID as a pkey, but can't yet since it needs unique values,
+-- and oracle can't set a default column value from a sequence
 alter table ATTACHMENT
- drop primary key ("DROPBOX_ID", "PATH");
+ add ("ATTACHMENT_ID" integer);
+
+-- fill in ATTACHMENT_ID with unique values from the sequence
+update ATTACHMENT set ATTACHMENT_ID = ATTACHMENT_ID_SEQ.nextval;
+
+-- now set ATTACHMENT_ID as primary key, which implies unique and not null
 alter table ATTACHMENT
- modify (DROPBOX_ID null);
-alter table ATTACHMENT
- add ("ATTACHMENT_ID" integer primary key);
+ add primary key(ATTACHMENT_ID);
 
 create table ATTACHMENT_CALENDAR_OBJECT (
     "ATTACHMENT_ID" integer not null references ATTACHMENT on delete cascade,

Modified: CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_13_to_14.sql
===================================================================
--- CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_13_to_14.sql	2013-03-15 21:46:49 UTC (rev 10936)
+++ CalendarServer/branches/users/gaya/directorybacker/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_13_to_14.sql	2013-03-15 21:47:42 UTC (rev 10937)
@@ -27,7 +27,7 @@
 alter table CALENDAR_BIND
  drop column SEEN_BY_SHAREE;
 alter table CALENDAR_BIND
- modify (ADDRESSBOOK_RESOURCE_NAME not null);
+ modify (CALENDAR_RESOURCE_NAME not null);
  
 alter table ADDRESSBOOK_BIND
  drop column SEEN_BY_OWNER;
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130315/c3cdf224/attachment-0001.html>


More information about the calendarserver-changes mailing list