[CalendarServer-changes] [2908] CalendarServer/branches/users/sagen/mailgateway-2906

source_changes at macosforge.org source_changes at macosforge.org
Thu Aug 28 22:11:44 PDT 2008


Revision: 2908
          http://trac.macosforge.org/projects/calendarserver/changeset/2908
Author:   sagen at apple.com
Date:     2008-08-28 22:11:43 -0700 (Thu, 28 Aug 2008)
Log Message:
-----------
Merge branch forward to include what's in trunk 2907

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd-test.plist
    CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd.plist
    CalendarServer/branches/users/sagen/mailgateway-2906/twisted/plugins/caldav.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/cluster.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/config.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/imip.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/static.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/tap.py

Added Paths:
-----------
    CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/Twisted/twisted.mail.imap4.patch
    CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/vobject/vobject.icalendar.patch
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/ical.jpg
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/mail.py
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/test_mail.py

Removed Paths:
-------------
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/ical.jpg
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics
    CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply

Property Changed:
----------------
    CalendarServer/branches/users/sagen/mailgateway-2906/


Property changes on: CalendarServer/branches/users/sagen/mailgateway-2906
___________________________________________________________________
Modified: svn:ignore
   - *.tgz
data
logs
build

   + *.tgz
data
logs
build
*.pyc
*.pyo


Modified: CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd-test.plist	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd-test.plist	2008-08-29 05:11:43 UTC (rev 2908)
@@ -413,21 +413,35 @@
   	<dict>
   	  <key>Enabled</key>
   	  <false/>
+  	  <key>MailGatewayServer</key>
+  	  <string>localhost</string>
+  	  <key>MailGatewayPort</key>
+  	  <integer>62310</integer>
   	  <key>Sending</key>
   	  <dict>
   	    <key>Server</key>
   	    <string></string>
   	    <key>Port</key>
   	    <integer>587</integer>
+  	    <key>Address</key>
+  	    <string></string> <!-- Address email will be sent from -->
       </dict>
       <key>Receiving</key>
       <dict>
   	    <key>Server</key>
   	    <string></string>
+  	    <key>UseSSL</key>
+  	    <true/>
   	    <key>Port</key>
-  	    <integer>0</integer>
+  	    <integer>995</integer>
   	    <key>Type</key>
-  	    <string></string> <!-- Either 'pop3' or 'imap' -->
+  	    <string></string> <!-- Either 'pop' or 'imap' -->
+  	    <key>PollingSeconds</key>
+  	    <integer>30</integer>
+  	    <key>Username</key>
+  	    <string></string>
+  	    <key>Password</key>
+  	    <string></string>
   	  </dict>
   	  <key>AddressPatterns</key>
   	  <array>

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd.plist	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/conf/caldavd.plist	2008-08-29 05:11:43 UTC (rev 2908)
@@ -386,21 +386,33 @@
   	<dict>
   	  <key>Enabled</key>
   	  <false/>
+  	  <key>MailGatewayServer</key>
+  	  <string>localhost</string>
+  	  <key>MailGatewayPort</key>
+  	  <integer>62310</integer>
   	  <key>Sending</key>
   	  <dict>
   	    <key>Server</key>
   	    <string></string>
   	    <key>Port</key>
   	    <integer>587</integer>
+  	    <key>Address</key>
+  	    <string></string> <!-- Address email will be sent from -->
       </dict>
       <key>Receiving</key>
       <dict>
   	    <key>Server</key>
   	    <string></string>
   	    <key>Port</key>
-  	    <integer>0</integer>
+  	    <integer>995</integer>
   	    <key>Type</key>
-  	    <string></string> <!-- Either 'pop3' or 'imap' -->
+  	    <string></string> <!-- Either 'pop' or 'imap' -->
+  	    <key>PollingSeconds</key>
+  	    <integer>30</integer>
+  	    <key>Username</key>
+  	    <string></string>
+  	    <key>Password</key>
+  	    <string></string>
   	  </dict>
   	  <key>AddressPatterns</key>
   	  <array>

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/Twisted/twisted.mail.imap4.patch (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/lib-patches/Twisted/twisted.mail.imap4.patch)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/Twisted/twisted.mail.imap4.patch	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/Twisted/twisted.mail.imap4.patch	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,50 @@
+Index: twisted/mail/imap4.py
+===================================================================
+--- twisted/mail/imap4.py	(revision 19773)
++++ twisted/mail/imap4.py	(working copy)
+@@ -363,16 +363,11 @@
+         for L in self.lines:
+             names = parseNestedParens(L)
+             N = len(names)
++            # This section is patched as described in http://twistedmatrix.com/trac/ticket/1105
+             if (N >= 1 and names[0] in self._1_RESPONSES or
++                N >= 2 and names[1] in self._2_RESPONSES or
+                 N >= 2 and names[0] == 'OK' and isinstance(names[1], types.ListType) and names[1][0] in self._OK_RESPONSES):
+                 send.append(L)
+-            elif N >= 3 and names[1] in self._2_RESPONSES:
+-                if isinstance(names[2], list) and len(names[2]) >= 1 and names[2][0] == 'FLAGS' and 'FLAGS' not in self.args:
+-                    unuse.append(L)
+-                else:
+-                    send.append(L)
+-            elif N >= 2 and names[1] in self._2_RESPONSES:
+-                send.append(L)
+             else:
+                 unuse.append(L)
+         d, self.defer = self.defer, None
+@@ -3336,6 +3331,8 @@
+                             if len(data) < 2:
+                                 raise IllegalServerResponse("Not enough arguments", data)
+                             flags.setdefault(id, {})[data[0]] = data[1]
++                            if data[0] == 'FLAGS':
++                                self.flagsChanged({id: data[1]})
+                             del data[:2]
+                 else:
+                     print '(2)Ignoring ', parts
+@@ -3431,7 +3428,16 @@
+                     except ValueError:
+                         raise IllegalServerResponse, line
+                     else:
+-                        info[id] = parseNestedParens(parts[2])
++                        data = parseNestedParens(parts[2])[0]
++                        # This section is patched as described in http://twistedmatrix.com/trac/ticket/1105
++                        # XXX this will fail if 'FLAGS' is a retrieved part
++                        for i in range(len(data) -1):
++                            if data[i] == 'FLAGS':
++                                self.flagsChanged({id: data[i+1]})
++                                del data[i:i+2]
++                                break
++                        if data:
++                            info.setdefault(id, []).append(data)
+         return info
+ 
+     def _fetch(self, messages, useUID=0, **terms):

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/vobject/vobject.icalendar.patch (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/lib-patches/vobject/vobject.icalendar.patch)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/vobject/vobject.icalendar.patch	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/lib-patches/vobject/vobject.icalendar.patch	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,60 @@
+Index: vobject/icalendar.py
+===================================================================
+--- vobject/icalendar.py	(revision 208)
++++ vobject/icalendar.py	(working copy)
+@@ -777,22 +777,27 @@
+     After transformation, value is a list of strings.
+     
+     """
++    listSeparator = ","
+ 
+-    @staticmethod
+-    def decode(line):
++    @classmethod
++    def decode(cls, line):
+         """Remove backslash escaping from line.value, then split on commas."""
+         if line.encoded:
+-            line.value = stringToTextValues(line.value)
++            line.value = stringToTextValues(line.value,
++                listSeparator=cls.listSeparator)
+             line.encoded=False
+     
+-    @staticmethod
+-    def encode(line):
++    @classmethod
++    def encode(cls, line):
+         """Backslash escape line.value."""
+         if not line.encoded:
+-            line.value = ','.join(backslashEscape(val) for val in line.value)
++            line.value = cls.listSeparator.join(backslashEscape(val) for val in line.value)
+             line.encoded=True
+     
+ 
++class SemicolonMultiTextBehavior(MultiTextBehavior):
++    listSeparator = ";"
++
+ #------------------------ Registered Behavior subclasses -----------------------
+ class VCalendar2_0(VCalendarComponentBehavior):
+     """vCalendar 2.0 behavior. With added VAVAILABILITY support."""
+@@ -804,6 +809,7 @@
+                      'METHOD':        (0, 1, None),
+                      'VERSION':       (0, 1, None),#required, but auto-generated
+                      'PRODID':        (1, 1, None),
++                     'REQUEST-STATUS': (0, None, None),
+                      'VTIMEZONE':     (0, None, None),
+                      'VEVENT':        (0, None, None),
+                      'VTODO':         (0, None, None),
+@@ -1481,11 +1487,12 @@
+ 
+ textList = ['CALSCALE', 'METHOD', 'PRODID', 'CLASS', 'COMMENT', 'DESCRIPTION',
+             'LOCATION', 'STATUS', 'SUMMARY', 'TRANSP', 'CONTACT', 'RELATED-TO',
+-            'UID', 'ACTION', 'REQUEST-STATUS', 'BUSYTYPE']
++            'UID', 'ACTION', 'BUSYTYPE']
+ map(lambda x: registerBehavior(TextBehavior, x), textList)
+ 
+ multiTextList = ['CATEGORIES', 'RESOURCES']
+ map(lambda x: registerBehavior(MultiTextBehavior, x), multiTextList)
++registerBehavior(SemicolonMultiTextBehavior, 'REQUEST-STATUS')
+ 
+ #------------------------ Serializing helper functions -------------------------
+ 

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twisted/plugins/caldav.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twisted/plugins/caldav.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twisted/plugins/caldav.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -32,3 +32,4 @@
 TwistedCalDAV = TAP('twistedcaldav.tap.CalDAVServiceMaker')
 
 CalDAVNotifier = TAP('twistedcaldav.notify.NotificationServiceMaker')
+CalDAVMailGateway = TAP('twistedcaldav.mail.MailGatewayServiceMaker')

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/cluster.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/cluster.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/cluster.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -323,7 +323,18 @@
         ]
         monitor.addProcess('notifications', notificationsArgv, env=parentEnv)
 
+    if (config.Scheduling["iMIP"]["Enabled"] and
+        config.Scheduling["iMIP"]["MailGatewayServer"] == "localhost"):
+        log.msg("Adding mail gateway service")
 
+        mailGatewayArgv = [
+            config.Twisted['twistd'],
+            '-n', 'caldav_mailgateway',
+            '-f', options['config'],
+        ]
+        monitor.addProcess('mailgateway', mailGatewayArgv, env=parentEnv)
+
+
     logger = AMPLoggingFactory(
         RotatingFileAccessLoggingObserver(config.AccessLogFile))
 

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/config.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/config.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -198,16 +198,23 @@
 
         "iMIP": {
             "Enabled"          : False, # Server-to-iMIP protocol
+            "MailGatewayServer" : "localhost",
+            "MailGatewayPort"   : 62310,
             "Sending": {
-                "Server"       : "",    # SMTP server to relay messages through
-                "Port"         : 587,   # SMTP server port to relay messages through
+                "Server"        : "",    # SMTP server to relay messages through
+                "Port"          : 587,   # SMTP server port to relay messages through
+                "Address"       : "",    # 'From' address for server
             },
             "Receiving": {
-                "Server"       : "",    # Server to retrieve email messages from
-                "Port"         : 0,     # Server port to retrieve email messages from
-                "Type"         : "",    # Type of message access server: 'pop3' or 'imap'
+                "Server"        : "",    # Server to retrieve email messages from
+                "Port"          : 0,     # Server port to retrieve email messages from
+                "UseSSL"        : True,
+                "Type"          : "",    # Type of message access server: 'pop' or 'imap'
+                "PollingSeconds"    : 30,  # How often to fetch mail
+                "Username"      : "",    # For account receving mail
+                "Password"      : "",    # For account receving mail
             },
-            "AddressPatterns"  : [],    # Reg-ex patterns to match iMIP-able calendar user addresses
+            "AddressPatterns"   : [],    # Reg-ex patterns to match iMIP-able calendar user addresses
         },
 
     },

Deleted: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/ical.jpg
===================================================================
(Binary files differ)

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/images/mail/ical.jpg (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/images/mail/ical.jpg)
===================================================================
(Binary files differ)

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/mail.py (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/mail.py)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/mail.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/mail.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,1151 @@
+##
+# Copyright (c) 2005-2008 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.
+##
+
+"""
+Mail Gateway for Calendar Server
+
+"""
+
+from twisted.internet import protocol, defer, ssl
+from twisted.web import resource, static, server, client
+from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue
+from twisted.protocols import basic
+from twisted.mail import pop3client, imap4
+from twisted.plugin import IPlugin
+from twisted.application import internet, service
+from twisted.python.usage import Options, UsageError
+from twisted.python.reflect import namedClass
+from twisted.mail.smtp import messageid, rfc822date, sendmail
+from twistedcaldav.log import Logger, LoggingMixIn
+from twistedcaldav import ical
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.scheduling.scheduler import IMIPScheduler
+from twistedcaldav.config import config, parseConfig, defaultConfig
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.ical import Property
+from zope.interface import Interface, implements
+import email, email.utils
+from email.mime.multipart import MIMEMultipart
+from email.mime.image import MIMEImage
+from email.mime.text import MIMEText
+import uuid
+import os
+import datetime
+import base64
+
+__all__ = [
+    "IMIPInboxResource",
+    "MailGatewayServiceMaker",
+    "MailGatewayTokensDatabase",
+    "MailHandler",
+]
+
+
+log = Logger()
+
+#
+# Mail gateway service config
+#
+
+class MailGatewayOptions(Options):
+    optParameters = [[
+        "config", "f", "/etc/caldavd/caldavd.plist", "Path to configuration file."
+    ]]
+
+    def __init__(self, *args, **kwargs):
+        super(MailGatewayOptions, self).__init__(*args, **kwargs)
+
+        self.overrides = {}
+
+    def _coerceOption(self, configDict, key, value):
+        """
+        Coerce the given C{val} to type of C{configDict[key]}
+        """
+        if key in configDict:
+            if isinstance(configDict[key], bool):
+                value = value == "True"
+
+            elif isinstance(configDict[key], (int, float, long)):
+                value = type(configDict[key])(value)
+
+            elif isinstance(configDict[key], (list, tuple)):
+                value = value.split(',')
+
+            elif isinstance(configDict[key], dict):
+                raise UsageError(
+                    "Dict options not supported on the command line"
+                )
+
+            elif value == 'None':
+                value = None
+
+        return value
+
+    def _setOverride(self, configDict, path, value, overrideDict):
+        """
+        Set the value at path in configDict
+        """
+        key = path[0]
+
+        if len(path) == 1:
+            overrideDict[key] = self._coerceOption(configDict, key, value)
+            return
+
+        if key in configDict:
+            if not isinstance(configDict[key], dict):
+                raise UsageError(
+                    "Found intermediate path element that is not a dictionary"
+                )
+
+            if key not in overrideDict:
+                overrideDict[key] = {}
+
+            self._setOverride(
+                configDict[key], path[1:],
+                value, overrideDict[key]
+            )
+
+
+    def opt_option(self, option):
+        """
+        Set an option to override a value in the config file. True, False, int,
+        and float options are supported, as well as comma seperated lists. Only
+        one option may be given for each --option flag, however multiple
+        --option flags may be specified.
+        """
+
+        if "=" in option:
+            path, value = option.split('=')
+            self._setOverride(
+                defaultConfig,
+                path.split('/'),
+                value,
+                self.overrides
+            )
+        else:
+            self.opt_option('%s=True' % (option,))
+
+    opt_o = opt_option
+
+    def postOptions(self):
+        parseConfig(self['config'])
+        config.updateDefaults(self.overrides)
+        self.parent['pidfile'] = None
+
+
+
+class IMIPInboxResource(CalDAVResource):
+    """
+    IMIP-delivery Inbox resource.
+
+    Extends L{DAVResource} to provide IMIP delivery functionality.
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
+
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+        self.parent = parent
+
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule for all principals (includes anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.All()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(caldavxml.Schedule()),
+                ),
+                davxml.Protected(),
+            ),
+        )
+
+    def resourceType(self):
+        return davxml.ResourceType.ischeduleinbox
+
+    def isCollection(self):
+        return False
+
+    def isCalendarCollection(self):
+        return False
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+    def render(self, request):
+        output = """<html>
+<head>
+<title>IMIP Delivery Resource</title>
+</head>
+<body>
+<h1>IMIP Delivery Resource.</h1>
+</body
+</html>"""
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
+
+    @inlineCallbacks
+    def http_POST(self, request):
+        """
+        The IMIP delivery POST method.
+        """
+
+        # Check authentication and access controls
+        # yield self.authorize(request, (caldavxml.Schedule(),))
+
+        # Inject using the IMIPScheduler.
+        scheduler = IMIPScheduler(request, self)
+
+        # Do the POST processing treating this as a non-local schedule
+        result = (yield scheduler.doSchedulingViaPOST())
+        returnValue(result.response())
+
+
+
+def injectMessage(organizer, attendee, calendar, msgId, reactor=None):
+
+    if reactor is None:
+        from twisted.internet import reactor
+
+    headers = {
+        'Content-Type' : 'text/calendar',
+        'Originator' : attendee,
+        'Recipient' : organizer,
+    }
+
+    data = str(calendar)
+
+    if config.SSLPort:
+        useSSL = True
+        port = config.SSLPort
+    else:
+        useSSL = False
+        port = config.HTTPPort
+
+    host = config.ServerHostName
+    path = "inbox"
+    scheme = "https:" if useSSL else "http:"
+    url = "%s//%s:%d/%s/" % (scheme, host, port, path)
+
+    log.debug("Injecting to %s: %s %s" % (url, str(headers), data))
+    factory = client.HTTPClientFactory(url, method='POST', headers=headers,
+        postdata=data)
+    if useSSL:
+        reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
+    else:
+        reactor.connectTCP(host, port, factory)
+
+    def _success(result, msgId):
+        log.info("Mail gateway successfully injected message %s" % (msgId,))
+
+    def _failure(failure, msgId):
+        log.err("Mail gateway failed to inject message %s (Reason: %s)" %
+            (msgId, failure.getErrorMessage()))
+
+    factory.deferred.addCallback(_success, msgId).addErrback(_failure, msgId)
+    return factory.deferred
+
+
+
+
+class MailGatewayTokensDatabase(AbstractSQLDatabase, LoggingMixIn):
+    """
+    A database to maintain "plus-address" tokens for IMIP requests.
+
+    SCHEMA:
+
+    Token Database:
+
+    ROW: TOKEN, ORGANIZER, ATTENDEE
+
+    """
+
+    dbType = "MAILGATEWAYTOKENS"
+    dbFilename = "mailgatewaytokens.sqlite"
+    dbFormatVersion = "1"
+
+
+    def __init__(self, path):
+        if path != ":memory:":
+            path = os.path.join(path, MailGatewayTokensDatabase.dbFilename)
+        super(MailGatewayTokensDatabase, self).__init__(path, True)
+
+    def createToken(self, organizer, attendee, token=None):
+        if token is None:
+            token = str(uuid.uuid4())
+        self._db_execute(
+            """
+            insert into TOKENS (TOKEN, ORGANIZER, ATTENDEE)
+            values (:1, :2, :3)
+            """, token, organizer, attendee
+        )
+        self._db_commit()
+        self.log_info("Mail gateway created token %s for %s (organizer) and %s (attendee)" % (token, organizer, attendee))
+        return token
+
+    def lookupByToken(self, token):
+        results = list(
+            self._db_execute(
+                """
+                select ORGANIZER, ATTENDEE from TOKENS
+                where TOKEN = :1
+                """, token
+            )
+        )
+
+        if len(results) != 1:
+            return None
+
+        return results[0]
+
+    def getToken(self, organizer, attendee):
+        token = self._db_value_for_sql(
+            """
+            select TOKEN from TOKENS
+            where ORGANIZER = :1 and ATTENDEE = :2
+            """, organizer, attendee
+        )
+        return token
+
+    def deleteToken(self, token):
+        self._db_execute(
+            """
+            delete from TOKENS where TOKEN = :1
+            """, token
+        )
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return MailGatewayTokensDatabase.dbFormatVersion
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return MailGatewayTokensDatabase.dbType
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+
+        #
+        # TOKENS table
+        #
+        q.execute(
+            """
+            create table TOKENS (
+                TOKEN       text,
+                ORGANIZER   text,
+                ATTENDEE    text
+            )
+            """
+        )
+        q.execute(
+            """
+            create index TOKENSINDEX on TOKENS (TOKEN)
+            """
+        )
+
+    def _db_upgrade_data_tables(self, q, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        @param q: a database cursor to use.
+        @param old_version: existing DB's version number
+        @type old_version: str
+        """
+        pass
+
+
+
+#
+# Service
+#
+
+class MailGatewayServiceMaker(LoggingMixIn):
+    implements(IPlugin, service.IServiceMaker)
+
+    tapname = "caldav_mailgateway"
+    description = "Mail Gateway"
+    options = MailGatewayOptions
+
+    def makeService(self, options):
+
+        multiService = service.MultiService()
+
+        settings = config.Scheduling['iMIP']
+        if settings['Enabled']:
+            mailer = MailHandler()
+
+            mailType = settings['Receiving']['Type']
+            if mailType.lower().startswith('pop'):
+                self.log_info("Starting Mail Gateway Service: POP3")
+                client = POP3Service(settings['Receiving'], mailer)
+            elif mailType.lower().startswith('imap'):
+                self.log_info("Starting Mail Gateway Service: IMAP4")
+                client = IMAP4Service(settings['Receiving'], mailer)
+            else:
+                # TODO: raise error?
+                self.log_error("Invalid iMIP type in configuration: %s" %
+                    (mailType,))
+                return multiService
+
+            client.setServiceParent(multiService)
+
+            IScheduleService(settings, mailer).setServiceParent(multiService)
+        else:
+            self.log_info("Mail Gateway Service not enabled")
+
+        return multiService
+
+
+#
+# ISchedule Inbox
+#
+class IScheduleService(service.Service, LoggingMixIn):
+
+    def __init__(self, settings, mailer):
+        self.settings = settings
+        self.mailer = mailer
+        root = resource.Resource()
+        root.putChild('', self.HomePage())
+        root.putChild('inbox', self.IScheduleInbox(mailer))
+        self.site = server.Site(root)
+        self.server = internet.TCPServer(settings['MailGatewayPort'], self.site)
+
+    def startService(self):
+        self.server.startService()
+
+    def stopService(self):
+        self.server.stopService()
+
+
+    class HomePage(resource.Resource):
+        def render(self, request):
+            return """
+            <html>
+            <head><title>ISchedule - IMIP Gateway</title></head>
+            <body>ISchedule - IMIP Gateway</body>
+            </html>
+            """
+
+    class IScheduleInbox(resource.Resource):
+
+        def __init__(self, mailer):
+            resource.Resource.__init__(self)
+            self.mailer = mailer
+
+        def render_GET(self, request):
+            return """
+            <html>
+            <head><title>ISchedule Inbox</title></head>
+            <body>ISchedule Inbox</body>
+            </html>
+            """
+
+        def render_POST(self, request):
+            # Compute token, add to db, generate email and send it
+            calendar = ical.Component.fromString(request.content.read())
+            headers = request.getAllHeaders()
+            self.mailer.outbound(headers['originator'], headers['recipient'],
+                calendar)
+
+            # TODO: what to return?
+            return """
+            <html>
+            <head><title>ISchedule Inbox</title></head>
+            <body>ISchedule Inbox</body>
+            </html>
+            """
+
+class MailHandler(LoggingMixIn):
+
+    def __init__(self, dataRoot=None):
+        if dataRoot is None:
+            dataRoot = config.DataRoot
+        self.db = MailGatewayTokensDatabase(dataRoot)
+
+    def checkDSN(self, message):
+        # returns (isDSN, Action, icalendar attachment)
+
+        report = deliveryStatus = original = calBody = None
+
+        for part in message.walk():
+            content_type = part.get_content_type()
+            if content_type == "multipart/report":
+                report = part
+                continue
+            elif content_type == "message/delivery-status":
+                deliveryStatus = part
+                continue
+            elif content_type == "message/rfc822":
+                original = part
+                continue
+            elif content_type == "text/calendar":
+                calBody = part.get_payload(decode=True)
+                continue
+
+        if report is not None and deliveryStatus is not None:
+            # we have what appears to be a DSN
+
+            lines = str(deliveryStatus).split("\n")
+            for line in lines:
+                lower = line.lower()
+                if lower.startswith("action:"):
+                    # found Action:
+                    action = lower.split(' ')[1]
+                    break
+            else:
+                action = None
+
+            return True, action, calBody
+
+        else:
+            # Not a DSN
+            return False, None, None
+
+
+    def _extractToken(self, text):
+        try:
+            pre, post = text.split('@')
+            pre, token = pre.split('+')
+            return token
+        except ValueError:
+            return None
+
+    def processDSN(self, calBody, msgId, fn):
+        calendar = ical.Component.fromString(calBody)
+        # Extract the token (from organizer property)
+        organizer = calendar.getOrganizer()
+        token = self._extractToken(organizer)
+        if not token:
+            self.log_error("Mail gateway can't find token in DSN %s" % (msgId,))
+            return
+
+        result = self.db.lookupByToken(token)
+        if result is None:
+            # This isn't a token we recognize
+            self.log_error("Mail gateway found a token (%s) but didn't recognize it in DSN %s" % (token, msgId))
+            return
+
+        organizer, attendee = result
+        organizer = str(organizer)
+        attendee = str(attendee)
+        calendar.removeAllButOneAttendee(attendee)
+        calendar.getOrganizerProperty().setValue(organizer)
+        for comp in calendar.subcomponents():
+            if comp.name() == "VEVENT":
+                comp.addProperty(Property("REQUEST-STATUS",
+                    ["5.1", "Service unavailable"]))
+                break
+        else:
+            # no VEVENT in the calendar body.
+            # TODO: what to do in this case?
+            pass
+
+        self.log_error("Mail gateway processing DSN %s" % (msgId,))
+        return fn(organizer, attendee, calendar, msgId)
+
+    def processReply(self, msg, fn):
+        # extract the token from the To header
+        name, addr = email.utils.parseaddr(msg['To'])
+        if addr:
+            # addr looks like: server_address+token at example.com
+            token = self._extractToken(addr)
+            if not token:
+                self.log_error("Mail gateway didn't find a token in message %s (%s)" % (msg['Message-ID'], msg['To']))
+                return
+        else:
+            self.log_error("Mail gateway couldn't parse To: address (%s) in message %s" % (msg['To'], msg['Message-ID']))
+            return
+
+        for part in msg.walk():
+            if part.get_content_type() == "text/calendar":
+                calBody = part.get_payload(decode=True)
+                break
+        else:
+            # No icalendar attachment
+            self.log_error("Mail gateway didn't find an icalendar attachment in message %s" % (msg['Message-ID'],))
+            return
+
+        self.log_debug(calBody)
+        calendar = ical.Component.fromString(calBody)
+
+        # process mail messages from POP or IMAP, inject to calendar server
+        result = self.db.lookupByToken(token)
+        if result is None:
+            # This isn't a token we recognize
+            self.log_error("Mail gateway found a token (%s) but didn't recognize it in message %s" % (token, msg['Message-ID']))
+            return
+
+        organizer, attendee = result
+        organizer = str(organizer)
+        attendee = str(attendee)
+        calendar.removeAllButOneAttendee(attendee)
+        calendar.getOrganizerProperty().setValue(organizer)
+        return fn(organizer, attendee, calendar, msg['Message-ID'])
+
+
+    def inbound(self, message, fn=injectMessage):
+        msg = email.message_from_string(message)
+
+        isDSN, action, calBody = self.checkDSN(msg)
+        if isDSN:
+            if action == 'failed' and calBody:
+                # This is a DSN we can handle
+                return self.processDSN(calBody, msg['Message-ID'], fn)
+            else:
+                # It's a DSN without enough to go on
+                self.log_error("Mail gateway can't process DSN %s" % (msg['Message-ID'],))
+                return
+
+        self.log_info("Mail gateway received message %s from %s to %s" %
+            (msg['Message-ID'], msg['From'], msg['To']))
+
+        return self.processReply(msg, fn)
+
+
+
+    def outbound(self, organizer, attendee, calendar):
+        # create token, send email
+        token = self.db.getToken(organizer, attendee)
+        if token is None:
+            token = self.db.createToken(organizer, attendee)
+
+        settings = config.Scheduling['iMIP']['Sending']
+        fullServerAddress = settings['Address']
+        name, serverAddress = email.utils.parseaddr(fullServerAddress)
+        pre, post = serverAddress.split('@')
+        addressWithToken = "%s+%s@%s" % (pre, token, post)
+        calendar.getOrganizerProperty().setValue("mailto:%s" %
+            (addressWithToken,))
+
+        msgId, message = self._generateTemplateMessage(calendar, organizer)
+
+        # The email's From: will be the calendar server's address (without
+        # + addressing), while the Reply-To: will be the organizer's email
+        # address.
+        if not organizer.startswith("mailto:"):
+            raise ValueError("ORGANIZER address '%s' must be mailto: for iMIP operation." % (organizer,))
+        organizer = organizer[7:]
+        fromAddr = serverAddress
+        toAddr = attendee
+        message = message.replace("${fromaddress}", fromAddr)
+        message = message.replace("${replytoaddress}", organizer)
+
+        if not attendee.startswith("mailto:"):
+            raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (attendee,))
+        attendee = attendee[7:]
+        message = message.replace("${toaddress}", attendee)
+
+        self.log_debug("Sending: %s" % (message,))
+        def _success(result, msgId, fromAddr, toAddr):
+            self.log_info("Mail gateway sent message %s from %s to %s" %
+                (msgId, fromAddr, toAddr))
+
+        def _failure(failure, msgId, fromAddr, toAddr):
+            self.log_error("Mail gateway failed to send message %s from %s to %s (Reason: %s)" %
+                (msgId, fromAddr, toAddr, failure.getErrorMessage()))
+
+        deferred = sendmail(settings['Server'], fromAddr, toAddr, message,
+            port=settings['Port'])
+
+        deferred.addCallback(_success, msgId, fromAddr, toAddr)
+        deferred.addErrback(_failure, msgId, fromAddr, toAddr)
+
+
+    def _generateTemplateMessage(self, calendar, organizer):
+
+        title, summary = self._generateCalendarSummary(calendar, organizer)
+
+        msg = MIMEMultipart()
+        msg["From"] = "${fromaddress}"
+        msg["Reply-To"] = "${replytoaddress}"
+        msg["To"] = "${toaddress}"
+        msg["Date"] = rfc822date()
+        msgId = messageid()
+        msg["Message-ID"] = msgId
+
+        msgAlt = MIMEMultipart("alternative")
+        msg.attach(msgAlt)
+
+        # plain text version
+        if calendar.propertyValue("METHOD") == "CANCEL":
+            msg["Subject"] = "Event cancelled"
+            plainText = u"An event has been cancelled.  Click the link below.\n"
+        else:
+            msg["Subject"] = "Event invitation: %s" % (title,)
+            plainText = u"You've been invited to the following event:  %s To accept or decline this invitation, click the link below.\n" % (summary,)
+
+        msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
+        msgAlt.attach(msgPlain)
+
+        # html version
+        msgHtmlRelated = MIMEMultipart("related", type="text/html")
+        msgAlt.attach(msgHtmlRelated)
+        htmlText = u"""
+<html><body><div>
+<img src="cid:ical.jpg">
+%s
+</div></body></html>
+""" % plainText
+        msgHtml = MIMEText(htmlText.encode("UTF-8"), "html", "UTF-8")
+        msgHtmlRelated.attach(msgHtml)
+
+        # an image for html version
+        imageName = "ical.jpg"
+        imageFile = open(os.path.join(os.path.dirname(__file__),
+            "images", "mail", imageName))
+        msgImage = MIMEImage(imageFile.read(),
+            _subtype='jpeg;x-apple-mail-type=stationery;name="%s"' %
+            (imageName,))
+        imageFile.close()
+        msgImage.add_header("Content-ID", "<%s>" % (imageName,))
+        msgImage.add_header("Content-Disposition", "inline;filename=%s" %
+            (imageName,))
+        msgHtmlRelated.attach(msgImage)
+
+        # the icalendar attachment
+        msgIcal = MIMEText(str(calendar), "calendar", "UTF-8")
+        msgIcal.add_header("Content-Disposition",
+            "attachment;filename=invitation.ics")
+        msg.attach(msgIcal)
+
+        return msgId, msg.as_string()
+
+    def _generateCalendarSummary(self, calendar, organizer):
+
+        # Get the most appropriate component
+        component = calendar.masterComponent()
+        if component is None:
+            component = calendar.mainComponent(True)
+
+        organizerProp = component.getOrganizerProperty()
+        if "CN" in organizerProp.params():
+            organizer = "%s <%s>" % (organizerProp.params()["CN"][0],
+                organizer,)
+
+        if calendar.propertyValue("METHOD") == "CANCEL":
+            dtinfo = ""
+        else:
+            dtinfo = self._getDateTimeInfo(component)
+
+        summary = component.propertyValue("SUMMARY")
+        if summary is None:
+            summary = ""
+
+        description = component.propertyValue("DESCRIPTION")
+        if description is None:
+            description = ""
+
+        return summary, """
+
+Summary: %s
+Organizer: %s
+%sDescription: %s
+
+""" % (summary, organizer, dtinfo, description,)
+
+    def _getDateTimeInfo(self, component):
+
+        dtstart = component.propertyNativeValue("DTSTART")
+        tzid_start = component.getProperty("DTSTART").params().get("TZID", "UTC")
+
+        dtend = component.propertyNativeValue("DTEND")
+        if dtend:
+            tzid_end = component.getProperty("DTEND").params().get("TZID", "UTC")
+            duration = dtend - dtstart
+        else:
+            duration = component.propertyNativeValue("DURATION")
+            if duration:
+                dtend = dtstart + duration
+                tzid_end = tzid_start
+            else:
+                if isinstance(dtstart, datetime.date):
+                    dtend = None
+                    duration = datetime.timedelta(days=1)
+                else:
+                    dtend = dtstart + datetime.timedelta(days=1)
+                    dtend.hour = dtend.minute = dtend.second = 0
+                    duration = dtend - dtstart
+        result = "Starts:      %s\n" % (self._getDateTimeText(dtstart, tzid_start),)
+        if dtend is not None:
+            result += "Ends:        %s\n" % (self._getDateTimeText(dtend, tzid_end),)
+        result += "Duration:    %s\n" % (self._getDurationText(duration),)
+
+        if not isinstance(dtstart, datetime.datetime):
+            result += "All Day\n"
+
+        for property_name in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
+            if component.hasProperty(property_name):
+                result += "Recurring\n"
+                break
+
+        return result
+
+    def _getDateTimeText(self, dtvalue, tzid):
+
+        if isinstance(dtvalue, datetime.datetime):
+            timeformat = "%A, %B %e, %Y %I:%M %p"
+        elif isinstance(dtvalue, datetime.date):
+            timeformat = "%A, %B %e, %Y"
+            tzid = ""
+        if tzid:
+            tzid = " (%s)" % (tzid,)
+
+        return "%s%s" % (dtvalue.strftime(timeformat), tzid,)
+
+    def _getDurationText(self, duration):
+
+        result = ""
+        if duration.days > 0:
+            result += "%d %s" % (
+                duration.days,
+                self._pluralize(duration.days, "day", "days")
+            )
+
+        hours = duration.seconds / 3600
+        minutes = divmod(duration.seconds / 60, 60)[1]
+        seconds = divmod(duration.seconds, 60)[1]
+
+        if hours > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                hours,
+                self._pluralize(hours, "hour", "hours")
+            )
+
+        if minutes > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                minutes,
+                self._pluralize(minutes, "minute", "minutes")
+            )
+
+        if seconds > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                seconds,
+                self._pluralize(seconds, "second", "seconds")
+            )
+
+        return result
+
+    def _pluralize(self, number, unit1, unitS):
+        return unit1 if number == 1 else unitS
+
+
+
+
+
+
+#
+# POP3
+#
+
+class POP3Service(service.Service, LoggingMixIn):
+
+    def __init__(self, settings, mailer):
+        if settings["UseSSL"]:
+            self.client = internet.SSLClient(settings["Server"],
+                settings["Port"],
+                POP3DownloadFactory(settings, mailer),
+                ssl.ClientContextFactory())
+        else:
+            self.client = internet.TCPClient(settings["Server"],
+                settings["Port"],
+                POP3DownloadFactory(settings, mailer))
+
+        self.mailer = mailer
+
+    def startService(self):
+        self.client.startService()
+
+    def stopService(self):
+        self.client.stopService()
+
+
+class POP3DownloadProtocol(pop3client.POP3Client, LoggingMixIn):
+    allowInsecureLogin = False
+
+    def serverGreeting(self, greeting):
+        self.log_debug("POP servergreeting")
+        pop3client.POP3Client.serverGreeting(self, greeting)
+        login = self.login(self.factory.settings["Username"],
+            self.factory.settings["Password"])
+        login.addCallback(self.cbLoggedIn)
+        login.addErrback(self.cbLoginFailed)
+
+    def cbLoginFailed(self, reason):
+        self.log_error("POP3 login failed for %s" %
+            (self.factory.settings["Username"],))
+        return self.quit()
+
+    def cbLoggedIn(self, result):
+        self.log_debug("POP loggedin")
+        return self.listSize().addCallback(self.cbGotMessageSizes)
+
+    def cbGotMessageSizes(self, sizes):
+        self.log_debug("POP gotmessagesizes")
+        downloads = []
+        for i in range(len(sizes)):
+            downloads.append(self.retrieve(i).addCallback(self.cbDownloaded, i))
+        return defer.DeferredList(downloads).addCallback(self.cbFinished)
+
+    def cbDownloaded(self, lines, id):
+        self.log_debug("POP downloaded message %d" % (id,))
+        self.factory.handleMessage("\r\n".join(lines))
+        self.log_debug("POP deleting message %d" % (id,))
+        self.delete(id)
+
+    def cbFinished(self, results):
+        self.log_debug("POP finished")
+        return self.quit()
+
+
+class POP3DownloadFactory(protocol.ClientFactory, LoggingMixIn):
+    protocol = POP3DownloadProtocol
+
+    def __init__(self, settings, mailer, reactor=None):
+        self.settings = settings
+        self.mailer = mailer
+        if reactor is None:
+            from twisted.internet import reactor
+        self.reactor = reactor
+        self.nextPoll = None
+
+    def retry(self, connector=None):
+        # TODO: if connector is None:
+
+        if connector is None:
+            if self.connector is None:
+                self.log_error("No connector to retry")
+                return
+            else:
+                connector = self.connector
+
+        def reconnector():
+            self.nextPoll = None
+            connector.connect()
+
+        self.log_debug("Scheduling next POP3 poll")
+        self.nextPoll = self.reactor.callLater(self.settings["PollingSeconds"],
+            reconnector)
+
+    def clientConnectionLost(self, connector, reason):
+        self.connector = connector
+        self.log_debug("POP factory connection lost")
+        self.retry(connector)
+
+
+    def clientConnectionFailed(self, connector, reason):
+        self.connector = connector
+        self.log_info("POP factory connection failed")
+        self.retry(connector)
+
+    def handleMessage(self, message):
+        self.log_debug("POP factory handle message")
+        self.log_debug(message)
+
+        return self.mailer.inbound(message)
+
+
+
+
+#
+# IMAP4
+#
+
+class IMAP4Service(service.Service):
+
+    def __init__(self, settings, mailer):
+
+        if settings["UseSSL"]:
+            self.client = internet.SSLClient(settings["Server"],
+                settings["Port"],
+                IMAP4DownloadFactory(settings, mailer),
+                ssl.ClientContextFactory())
+        else:
+            self.client = internet.TCPClient(settings["Server"],
+                settings["Port"],
+                IMAP4DownloadFactory(settings, mailer))
+
+        self.mailer = mailer
+
+    def startService(self):
+        self.client.startService()
+
+    def stopService(self):
+        self.client.stopService()
+
+
+class IMAP4DownloadProtocol(imap4.IMAP4Client, LoggingMixIn):
+
+    def serverGreeting(self, capabilities):
+        self.log_debug("IMAP servergreeting")
+        login = self.login(self.factory.settings["Username"],
+            self.factory.settings["Password"])
+        login.addCallback(self.cbLoggedIn)
+        login.addErrback(self.ebLoginFailed)
+
+    def ebLogError(self, error):
+        self.log_error("IMAP Error: %s" % (error,))
+
+    def ebLoginFailed(self, reason):
+        self.log_error("IMAP login failed for %s" %
+            (self.factory.settings["Username"],))
+        self.transport.loseConnection()
+
+    def cbLoggedIn(self, result):
+        self.log_debug("IMAP logged in [%s]" % (self.state,))
+        self.select("Inbox").addCallback(self.cbInboxSelected)
+
+    def cbInboxSelected(self, result):
+        self.log_debug("IMAP Inbox selected [%s]" % (self.state,))
+        allMessages = imap4.MessageSet(1, None)
+        self.fetchUID(allMessages, True).addCallback(self.cbGotUIDs)
+
+    def cbGotUIDs(self, results):
+        self.log_debug("IMAP got uids [%s]" % (self.state,))
+        self.messageUIDs = [result['UID'] for result in results.values()]
+        self.messageCount = len(self.messageUIDs)
+        self.log_debug("IMAP Inbox has %d messages" % (self.messageCount,))
+        if self.messageCount:
+            self.fetchNextMessage()
+        else:
+            # No messages; close it out
+            self.close().addCallback(self.cbClosed)
+
+    def fetchNextMessage(self):
+        self.log_debug("IMAP in fetchnextmessage [%s]" % (self.state,))
+        if self.messageUIDs:
+            nextUID = self.messageUIDs.pop(0)
+            messageListToFetch = imap4.MessageSet(nextUID)
+            self.log_debug("Downloading message %d of %d (%s)" %
+                (self.messageCount - len(self.messageUIDs), self.messageCount,
+                nextUID))
+            self.fetchMessage(messageListToFetch, True).addCallback(
+                self.cbGotMessage, messageListToFetch).addErrback(self.ebLogError)
+        else:
+            self.log_debug("Seeing if anything new has arrived")
+            # Go back and see if any more messages have come in
+            self.expunge().addCallback(self.cbInboxSelected)
+
+    def cbGotMessage(self, results, messageList):
+        self.log_debug("IMAP in cbGotMessage [%s]" % (self.state,))
+        try:
+            messageData = results.values()[0]['RFC822']
+        except IndexError:
+            # results will be empty unless the "twistedmail-imap-flags-anywhere"
+            # patch from http://twistedmatrix.com/trac/ticket/1105 is applied
+            self.log_error("Skipping empty results -- apply twisted patch!")
+            self.fetchNextMessage()
+            return
+
+        d = self.factory.handleMessage(messageData)
+        if isinstance(d, defer.Deferred):
+            d.addCallback(self.cbFlagDeleted, messageList)
+        else:
+            # No deferred returned, so no need for addCallback( )
+            self.cbFlagDeleted(None, messageList)
+
+    def cbFlagDeleted(self, results, messageList):
+        self.addFlags(messageList, ("\\Deleted",),
+            uid=True).addCallback(self.cbMessageDeleted, messageList)
+
+    def cbMessageDeleted(self, results, messageList):
+        self.log_debug("IMAP in cbMessageDeleted [%s]" % (self.state,))
+        self.log_debug("Deleted message")
+        self.fetchNextMessage()
+
+    def cbClosed(self, results):
+        self.log_debug("IMAP in cbClosed [%s]" % (self.state,))
+        self.log_debug("Mailbox closed")
+        self.logout().addCallback(
+            lambda _: self.transport.loseConnection())
+
+    def rawDataReceived(self, data):
+        self.log_debug("RAW RECEIVED: %s" % (data,))
+        imap4.IMAP4Client.rawDataReceived(self, data)
+
+    def lineReceived(self, line):
+        self.log_debug("RECEIVED: %s" % (line,))
+        imap4.IMAP4Client.lineReceived(self, line)
+
+    def sendLine(self, line):
+        self.log_debug("SENDING: %s" % (line,))
+        imap4.IMAP4Client.sendLine(self, line)
+
+
+class IMAP4DownloadFactory(protocol.ClientFactory, LoggingMixIn):
+    protocol = IMAP4DownloadProtocol
+
+    def __init__(self, settings, mailer, reactor=None):
+        self.log_debug("Setting up IMAPFactory")
+
+        self.settings = settings
+        self.mailer = mailer
+        if reactor is None:
+            from twisted.internet import reactor
+        self.reactor = reactor
+
+
+    def handleMessage(self, message):
+        self.log_debug("IMAP factory handle message")
+        self.log_debug(message)
+
+        return self.mailer.inbound(message)
+
+
+    def retry(self, connector=None):
+        # TODO: if connector is None:
+
+        if connector is None:
+            if self.connector is None:
+                self.log_error("No connector to retry")
+                return
+            else:
+                connector = self.connector
+
+        def reconnector():
+            self.nextPoll = None
+            connector.connect()
+
+        self.log_debug("Scheduling next IMAP4 poll")
+        self.nextPoll = self.reactor.callLater(self.settings["PollingSeconds"],
+            reconnector)
+
+    def clientConnectionLost(self, connector, reason):
+        self.connector = connector
+        self.log_debug("IMAP factory connection lost")
+        self.retry(connector)
+
+    def clientConnectionFailed(self, connector, reason):
+        self.connector = connector
+        self.log_error("IMAP factory connection failed")
+        self.retry(connector)

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/imip.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/imip.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/imip.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -25,6 +25,7 @@
 from twisted.web2 import responsecode
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.http import HTTPError
+from twisted.web import client
 
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
@@ -61,22 +62,18 @@
             if self.freebusy:
                 raise ValueError("iMIP VFREEBUSY REQUESTs not supported.")
 
-            message = self._generateTemplateMessage(self.scheduler.calendar)
-            fromAddr = self.scheduler.originator.cuaddr
-            if not fromAddr.startswith("mailto:"):
-                raise ValueError("ORGANIZER address '%s' must be mailto: for iMIP operation." % (fromAddr,))
-            fromAddr = fromAddr[7:]
-            message = message.replace("${fromaddress}", fromAddr)
-            
+            caldata = str(self.scheduler.calendar)
+
             for recipient in self.recipients:
                 try:
-                    toAddr = recipient.cuaddr
+                    toAddr = str(recipient.cuaddr)
                     if not toAddr.startswith("mailto:"):
                         raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (toAddr,))
-                    toAddr = toAddr[7:]
-                    sendit = message.replace("${toaddress}", toAddr)
-                    log.debug("Sending iMIP message To: '%s', From :'%s'\n%s" % (toAddr, fromAddr, sendit,))
-                    yield sendmail(config.Scheduling[self.serviceType()]["Sending"]["Server"], fromAddr, toAddr, sendit, port=config.Scheduling[self.serviceType()]["Sending"]["Port"])
+
+                    fromAddr = str(self.scheduler.calendar.getOrganizer())
+
+                    log.debug("POSTing iMIP message to gateway...  To: '%s', From :'%s'\n%s" % (toAddr, fromAddr, caldata,))
+                    yield self.postToGateway(fromAddr, toAddr, caldata)
         
                 except Exception, e:
                     # Generated failed response for this recipient
@@ -94,166 +91,20 @@
                 err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-failed")))
                 self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.1;Service unavailable")
 
-    def _generateTemplateMessage(self, calendar):
+    def postToGateway(self, fromAddr, toAddr, caldata, reactor=None):
+        if reactor is None:
+            from twisted.internet import reactor
 
-        caldata = str(calendar)
-        data = cStringIO.StringIO()
-        writer = MimeWriter.MimeWriter(data)
-    
-        writer.addheader("From", "${fromaddress}")
-        writer.addheader("To", "${toaddress}")
-        writer.addheader("Date", rfc822date())
-        writer.addheader("Subject", "DO NOT REPLY: calendar invitation test")
-        writer.addheader("Message-ID", messageid())
-        writer.addheader("Mime-Version", "1.0")
-        writer.flushheaders()
-    
-        writer.startmultipartbody("mixed")
-    
-        # message body
-        part = writer.nextpart()
-        body = part.startbody("text/plain")
-        body.write("""Hi,
-You've been invited to a cool event by CalendarServer's new iMIP processor.
+        mailGatewayServer = config.Scheduling['iMIP']['MailGatewayServer']
+        mailGatewayPort = config.Scheduling['iMIP']['MailGatewayPort']
+        url = "http://%s:%d/inbox" % (mailGatewayServer, mailGatewayPort)
+        headers = {
+            'Content-Type' : 'text/calendar',
+            'Originator' : fromAddr,
+            'Recipient' : toAddr,
+        }
+        factory = client.HTTPClientFactory(url, method='POST', headers=headers,
+            postdata=caldata)
+        reactor.connectTCP(mailGatewayServer, mailGatewayPort, factory)
+        return factory.deferred
 
-%s
-""" % (self._generateCalendarSummary(calendar),))
-    
-        part = writer.nextpart()
-        encoding = "7bit"
-        for i in caldata:
-            if ord(i) > 127:
-                encoding = "base64"
-                caldata = base64.encodestring(caldata)
-                break
-        part.addheader("Content-Transfer-Encoding", encoding)
-        body = part.startbody("text/calendar; charset=utf-8")
-        body.write(caldata.replace("\r", ""))
-    
-        # finish
-        writer.lastpart()
-
-        return data.getvalue()
-
-    def _generateCalendarSummary(self, calendar):
-
-        # Get the most appropriate component
-        component = calendar.masterComponent()
-        if component is None:
-            component = calendar.mainComponent(True)
-            
-        organizer = component.getOrganizerProperty()
-        if "CN" in organizer.params():
-            organizer = "%s <%s>" % (organizer.params()["CN"][0], organizer.value(),)
-        else:
-            organizer = organizer.value()
-            
-        dtinfo = self._getDateTimeInfo(component)
-        
-        summary = component.propertyValue("SUMMARY")
-        if summary is None:
-            summary = ""
-
-        description = component.propertyValue("DESCRIPTION")
-        if description is None:
-            description = ""
-
-        return """---- Begin Calendar Event Summary ----
-
-Organizer:   %s
-Summary:     %s
-%sDescription: %s
-
-----  End Calendar Event Summary  ----
-""" % (organizer, summary, dtinfo, description,)
-
-    def _getDateTimeInfo(self, component):
-        
-        dtstart = component.propertyNativeValue("DTSTART")
-        tzid_start = component.getProperty("DTSTART").params().get("TZID", "UTC")
-
-        dtend = component.propertyNativeValue("DTEND")
-        if dtend:
-            tzid_end = component.getProperty("DTEND").params().get("TZID", "UTC")
-            duration = dtend - dtstart
-        else:
-            duration = component.propertyNativeValue("DURATION")
-            if duration:
-                dtend = dtstart + duration
-                tzid_end = tzid_start
-            else:
-                if isinstance(dtstart, datetime.date):
-                    dtend = None
-                    duration = datetime.timedelta(days=1)
-                else:
-                    dtend = dtstart + datetime.timedelta(days=1)
-                    dtend.hour = dtend.minute = dtend.second = 0
-                    duration = dtend - dtstart
-        result = "Starts:      %s\n" % (self._getDateTimeText(dtstart, tzid_start),)
-        if dtend is not None:
-            result += "Ends:        %s\n" % (self._getDateTimeText(dtend, tzid_end),)
-        result += "Duration:    %s\n" % (self._getDurationText(duration),)
-        
-        if not isinstance(dtstart, datetime.datetime):
-            result += "All Day\n"
-        
-        for property_name in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
-            if component.hasProperty(property_name):
-                result += "Recurring\n"
-                break
-            
-        return result
-
-    def _getDateTimeText(self, dtvalue, tzid):
-        
-        if isinstance(dtvalue, datetime.datetime):
-            timeformat = "%A, %B %e, %Y %I:%M %p"
-        elif isinstance(dtvalue, datetime.date):
-            timeformat = "%A, %B %e, %Y"
-            tzid = ""
-        if tzid:
-            tzid = " (%s)" % (tzid,)
-
-        return "%s%s" % (dtvalue.strftime(timeformat), tzid,)
-        
-    def _getDurationText(self, duration):
-        
-        result = ""
-        if duration.days > 0:
-            result += "%d %s" % (
-                duration.days,
-                self._pluralize(duration.days, "day", "days")
-            )
-
-        hours = duration.seconds / 3600
-        minutes = divmod(duration.seconds / 60, 60)[1]
-        seconds = divmod(duration.seconds, 60)[1]
-        
-        if hours > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                hours,
-                self._pluralize(hours, "hour", "hours")
-            )
-        
-        if minutes > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                minutes,
-                self._pluralize(minutes, "minute", "minutes")
-            )
-        
-        if seconds > 0:
-            if result:
-                result += ", "
-            result += "%d %s" % (
-                seconds,
-                self._pluralize(seconds, "second", "seconds")
-            )
-
-        return result
-
-    def _pluralize(self, number, unit1, unitS):
-        return unit1 if number == 1 else unitS

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/scheduler.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/scheduling/scheduler.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -79,14 +79,14 @@
     
         self.method = "POST"
 
-        # Do some extra authorization checks
-        self.checkAuthorization()
-
         # Load various useful bits doing some basic checks on those
         self.loadOriginatorFromRequestHeaders()
         self.loadRecipientsFromRequestHeaders()
         yield self.loadCalendarFromRequest()
 
+        # Do some extra authorization checks
+        self.checkAuthorization()
+
         result = (yield self.doScheduling())
         returnValue(result)
 
@@ -97,15 +97,15 @@
     
         self.method = "PUT"
 
-        # Do some extra authorization checks
-        self.checkAuthorization()
-
         # Load various useful bits doing some basic checks on those
         self.originator = originator
         self.recipients = recipients
         self.calendar = calendar
         self.internal_request = internal_request
 
+        # Do some extra authorization checks
+        self.checkAuthorization()
+
         return self.doScheduling()
 
     @inlineCallbacks
@@ -714,6 +714,90 @@
             log.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),))
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
 
+class IMIPScheduler(Scheduler):
+
+    def checkAuthorization(self):
+        pass
+
+    def checkOrganizer(self):
+        pass
+
+    def checkOrganizerAsOriginator(self):
+        pass
+
+    def checkAttendeeAsOriginator(self):
+        pass
+
+    def securityChecks(self):
+        """
+        Check that the connection is from the mail gateway
+        """
+        allowed = config.Scheduling['iMIP']['MailGatewayServer']
+        # Get the request IP and map to hostname.
+        clientip = self.request.remoteAddr.host
+        host, aliases, _ignore_ips = socket.gethostbyaddr(clientip)
+        for host in itertools.chain((host,), aliases):
+            if host == allowed:
+                break
+        else:
+            log.err("Only %s is allowed to submit internal scheduling requests, not %s" % (allowed, host))
+            # TODO: verify this is the right response:
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+    @inlineCallbacks
+    def checkOriginator(self):
+        """
+        Check the validity of the Originator header.
+        """
+    
+        # For remote requests we do not allow the originator to be a local user or one within our domain.
+        originatorPrincipal = self.resource.principalForCalendarUserAddress(self.originator)
+        localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
+        if originatorPrincipal or localUser:
+            log.err("Cannot use originator that is on this server: %s" % (self.originator,))
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+        else:
+            self.originator = RemoteCalendarUser(self.originator)
+
+    @inlineCallbacks
+    def checkRecipients(self):
+        """
+        Check the validity of the Recipient header values. These must all be local as there
+        is no concept of server-to-server relaying.
+        """
+        
+        results = []
+        for recipient in self.recipients:
+            # Get the principal resource for this recipient
+            principal = self.resource.principalForCalendarUserAddress(recipient)
+            
+            # If no principal we may have a remote recipient but we should check whether
+            # the address is one that ought to be on our server and treat that as a missing
+            # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+            if principal is None:
+                localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(recipient))
+                if localUser:
+                    log.err("No principal for calendar user address: %s" % (recipient,))
+                else:
+                    log.err("Unknown calendar user address: %s" % (recipient,))
+                results.append(InvalidCalendarUser(recipient))
+            else:
+                # Map recipient to their inbox
+                inbox = None
+                inboxURL = principal.scheduleInboxURL()
+                if inboxURL:
+                    inbox = (yield self.request.locateResource(inboxURL))
+
+                if inbox:
+                    results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL))
+                else:
+                    log.err("No schedule inbox for principal: %s" % (principal,))
+                    results.append(InvalidCalendarUser(recipient))
+        
+        self.recipients = results
+
+
+
 class ScheduleResponseResponse (Response):
     """
     ScheduleResponse L{Response} object.

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/static.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/static.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -24,6 +24,7 @@
     "CalendarHomeProvisioningFile",
     "CalendarHomeUIDProvisioningFile",
     "CalendarHomeFile",
+    "IMIPInboxFile",
     "ScheduleFile",
     "ScheduleInboxFile",
     "ScheduleOutboxFile",
@@ -64,6 +65,7 @@
 from twistedcaldav.index import Index, IndexSchedule
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, IScheduleInboxResource
+from twistedcaldav.mail import IMIPInboxResource
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
 from twistedcaldav.directory.calendar import uidsResourceName
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
@@ -792,6 +794,41 @@
             (caldav_namespace, "calendar-collection-location-ok")
         )
 
+class IMIPInboxFile (IMIPInboxResource, CalDAVFile):
+    """
+    Mail gateway IMIP-delivery resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+        IMIPInboxResource.__init__(self, parent)
+        
+        self.fp.open("w").close()
+        self.fp.restat(False)
+
+    def __repr__(self):
+        return "<%s (IMIP delivery resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+    def isCollection(self):
+        return False
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return responsecode.NOT_FOUND
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
 class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
     """
     Free-busy URL resource.

Modified: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/tap.py	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/tap.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -54,6 +54,7 @@
 from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.static import IScheduleInboxFile
 from twistedcaldav.static import TimezoneServiceFile
+from twistedcaldav.static import IMIPInboxFile
 from twistedcaldav.timezones import TimezoneCache
 from twistedcaldav import pdmonster
 from twistedcaldav import memcachepool
@@ -438,6 +439,7 @@
     principalResourceClass       = DirectoryPrincipalProvisioningResource
     calendarResourceClass        = CalendarHomeProvisioningFile
     iScheduleResourceClass       = IScheduleInboxFile
+    imipResourceClass            = IMIPInboxFile
     timezoneServiceResourceClass = TimezoneServiceFile
 
     def makeService_Slave(self, options):
@@ -542,12 +544,22 @@
             log.msg("Setting up iSchedule inbox resource: %r" % (self.iScheduleResourceClass,))
     
             ischedule = self.iScheduleResourceClass(
-                os.path.join(config.DocumentRoot, 'inbox'),
+                os.path.join(config.DocumentRoot, 'ischedule'),
                 root,
             )
-            root.putChild('inbox', ischedule)
+            root.putChild('ischedule', ischedule)
 
         #
+        # IMIP delivery resource
+        #
+        imipInbox = self.imipResourceClass(
+            os.path.join(config.DocumentRoot, 'inbox'),
+            root,
+        )
+        root.putChild('inbox', imipInbox)
+
+
+        #
         # Configure ancillary data
         #
         log.info("Setting up Timezone Cache")

Deleted: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_no_ics	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics	2008-08-29 05:11:43 UTC (rev 2908)
@@ -1,108 +0,0 @@
-Return-path: <>
-Received: from elderberry.example.com ([17.128.115.181])
- by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
- 14 2008; 32bit)) with ESMTP id <0K5900HRN6UVQR40 at mail4.example.com> for
- xyzzy at example.com; Thu, 07 Aug 2008 16:06:31 -0700 (PDT)
-Original-recipient: rfc822;xyzzy at example.com
-Received: from relay12.example.com ([17.128.113.53])
- by elderberry.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb
- 28 2007)) with ESMTP id <0K5900H5D6UVQN60 at elderberry.example.com> for
- xyzzy at example.com (ORCPT xyzzy at example.com); Thu, 07 Aug 2008 16:06:31 -0700 (PDT)
-Received: from relay12.example.com (unknown [127.0.0.1])
-	by relay12.example.com (Symantec Mail Security) with ESMTP id E082C464003	for
- <xyzzy at example.com>; Thu, 07 Aug 2008 16:06:30 -0700 (PDT)
-Received: from mail-out4.example.com (mail-out4.example.com [17.254.13.23])
-	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
-	(No client certificate requested)	by relay12.example.com (example SCV relay)
- with ESMTP id B2942420006	for <xyzzy at example.com>; Thu,
- 07 Aug 2008 16:06:30 -0700 (PDT)
-Received: by mail-out4.example.com (Postfix)	id A18D2374C3E7; Thu,
- 07 Aug 2008 16:06:30 -0700 (PDT)
-Date: Thu, 07 Aug 2008 16:06:30 -0700 (PDT)
-From: MAILER-DAEMON at mail-out4.example.com (Mail Delivery System)
-Subject: Undelivered Mail Returned to Sender
-To: xyzzy at example.com
-Message-id: <20080807230630.A18D2374C3E7 at mail-out4.example.com>
-Auto-submitted: auto-replied
-MIME-version: 1.0
-Content-type: multipart/report; report-type=delivery-status;
- boundary="4B6C6374C3E5.1218150390/mail-out4.example.com"
-X-AuditID: 11807135-a7df5bb000001321-97-489b7ff6fce0
-X-Brightmail-Tracker: AAAAAA==
-
-This is a MIME-encapsulated message.
-
---4B6C6374C3E5.1218150390/mail-out4.example.com
-Content-Description: Notification
-Content-Type: text/plain; charset=us-ascii
-
-This is the mail system at host mail-out4.example.com.
-
-I'm sorry to have to inform you that your message could not
-be delivered to one or more recipients. It's attached below.
-
-For further assistance, please send mail to postmaster.
-
-If you do so, please include this problem report. You can
-delete your own text from the attached returned message.
-
-                   The mail system
-
-<thisdoesnotexist at example.com>: host
-    example-smtp-in.l.example.com[209.85.201.27] said: 550-5.1.1 The email account
-    that you tried to reach does not exist. Please 550-5.1.1 try
-    double-checking the recipient's email address for typos 550-5.1.1 or
-    unnecessary spaces. Learn more at                      550 5.1.1
-    http://mail.example.com/support/bin/answer.py?answer=6596 20si599639wfi.11
-    (in reply to RCPT TO command)
-
---4B6C6374C3E5.1218150390/mail-out4.example.com
-Content-Description: Delivery report
-Content-Type: message/delivery-status
-
-Reporting-MTA: dns; mail-out4.example.com
-X-Postfix-Queue-ID: 4B6C6374C3E5
-X-Postfix-Sender: rfc822; xyzzy at example.com
-Arrival-Date: Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
-
-Final-Recipient: rfc822; thisdoesnotexist at example.com
-Original-Recipient: rfc822;thisdoesnotexist at example.com
-Action: failed
-Status: 5.1.1
-Remote-MTA: dns; example-smtp-in.l.example.com
-Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does
-    not exist. Please 550-5.1.1 try double-checking the recipient's email
-    address for typos 550-5.1.1 or unnecessary spaces. Learn more at
-    550 5.1.1 http://mail.example.com/support/bin/answer.py?answer=6596
-    20si599639wfi.11
-
---4B6C6374C3E5.1218150390/mail-out4.example.com
-Content-Description: Undelivered Message
-Content-Type: message/rfc822
-
-Received: from relay13.example.com (relay13.example.com [17.128.113.29])
-	by mail-out4.example.com (Postfix) with ESMTP id 4B6C6374C3E5
-	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
-Received: from relay13.example.com (unknown [127.0.0.1])
-	by relay13.example.com (Symantec Mail Security) with ESMTP id 347CD280A3
-	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
-X-AuditID: 1180711d-a2ff7bb000000ece-13-489b7ff6536b
-Received: from plugh.example.com (plugh.example.com [17.224.21.17])
-	(using TLSv1 with cipher AES128-SHA (128/128 bits))
-	(No client certificate requested)
-	by relay13.example.com (example SCV relay) with ESMTP id 1C5C928094
-	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
-Message-Id: <40900559-DAB1-4956-BA87-F88E00CF5104 at example.com>
-From: xyzzy <xyzzy at example.com>
-To: thisdoesnotexist at example.com
-Content-Type: text/plain; charset=US-ASCII; format=flowed
-Content-Transfer-Encoding: 7bit
-Mime-Version: 1.0 (example Message framework v928.1)
-Subject: testing
-Date: Thu, 7 Aug 2008 16:06:29 -0700
-X-Mailer: example Mail (2.928.1)
-X-Brightmail-Tracker: AAAAAA==
-
-asdf
-
---4B6C6374C3E5.1218150390/mail-out4.example.com--

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_no_ics)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_ics	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,108 @@
+Return-path: <>
+Received: from elderberry.example.com ([17.128.115.181])
+ by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
+ 14 2008; 32bit)) with ESMTP id <0K5900HRN6UVQR40 at mail4.example.com> for
+ xyzzy at example.com; Thu, 07 Aug 2008 16:06:31 -0700 (PDT)
+Original-recipient: rfc822;xyzzy at example.com
+Received: from relay12.example.com ([17.128.113.53])
+ by elderberry.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb
+ 28 2007)) with ESMTP id <0K5900H5D6UVQN60 at elderberry.example.com> for
+ xyzzy at example.com (ORCPT xyzzy at example.com); Thu, 07 Aug 2008 16:06:31 -0700 (PDT)
+Received: from relay12.example.com (unknown [127.0.0.1])
+	by relay12.example.com (Symantec Mail Security) with ESMTP id E082C464003	for
+ <xyzzy at example.com>; Thu, 07 Aug 2008 16:06:30 -0700 (PDT)
+Received: from mail-out4.example.com (mail-out4.example.com [17.254.13.23])
+	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+	(No client certificate requested)	by relay12.example.com (example SCV relay)
+ with ESMTP id B2942420006	for <xyzzy at example.com>; Thu,
+ 07 Aug 2008 16:06:30 -0700 (PDT)
+Received: by mail-out4.example.com (Postfix)	id A18D2374C3E7; Thu,
+ 07 Aug 2008 16:06:30 -0700 (PDT)
+Date: Thu, 07 Aug 2008 16:06:30 -0700 (PDT)
+From: MAILER-DAEMON at mail-out4.example.com (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+To: xyzzy at example.com
+Message-id: <20080807230630.A18D2374C3E7 at mail-out4.example.com>
+Auto-submitted: auto-replied
+MIME-version: 1.0
+Content-type: multipart/report; report-type=delivery-status;
+ boundary="4B6C6374C3E5.1218150390/mail-out4.example.com"
+X-AuditID: 11807135-a7df5bb000001321-97-489b7ff6fce0
+X-Brightmail-Tracker: AAAAAA==
+
+This is a MIME-encapsulated message.
+
+--4B6C6374C3E5.1218150390/mail-out4.example.com
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+This is the mail system at host mail-out4.example.com.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+                   The mail system
+
+<thisdoesnotexist at example.com>: host
+    example-smtp-in.l.example.com[209.85.201.27] said: 550-5.1.1 The email account
+    that you tried to reach does not exist. Please 550-5.1.1 try
+    double-checking the recipient's email address for typos 550-5.1.1 or
+    unnecessary spaces. Learn more at                      550 5.1.1
+    http://mail.example.com/support/bin/answer.py?answer=6596 20si599639wfi.11
+    (in reply to RCPT TO command)
+
+--4B6C6374C3E5.1218150390/mail-out4.example.com
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mail-out4.example.com
+X-Postfix-Queue-ID: 4B6C6374C3E5
+X-Postfix-Sender: rfc822; xyzzy at example.com
+Arrival-Date: Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
+
+Final-Recipient: rfc822; thisdoesnotexist at example.com
+Original-Recipient: rfc822;thisdoesnotexist at example.com
+Action: failed
+Status: 5.1.1
+Remote-MTA: dns; example-smtp-in.l.example.com
+Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does
+    not exist. Please 550-5.1.1 try double-checking the recipient's email
+    address for typos 550-5.1.1 or unnecessary spaces. Learn more at
+    550 5.1.1 http://mail.example.com/support/bin/answer.py?answer=6596
+    20si599639wfi.11
+
+--4B6C6374C3E5.1218150390/mail-out4.example.com
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Received: from relay13.example.com (relay13.example.com [17.128.113.29])
+	by mail-out4.example.com (Postfix) with ESMTP id 4B6C6374C3E5
+	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
+Received: from relay13.example.com (unknown [127.0.0.1])
+	by relay13.example.com (Symantec Mail Security) with ESMTP id 347CD280A3
+	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
+X-AuditID: 1180711d-a2ff7bb000000ece-13-489b7ff6536b
+Received: from plugh.example.com (plugh.example.com [17.224.21.17])
+	(using TLSv1 with cipher AES128-SHA (128/128 bits))
+	(No client certificate requested)
+	by relay13.example.com (example SCV relay) with ESMTP id 1C5C928094
+	for <thisdoesnotexist at example.com>; Thu,  7 Aug 2008 16:06:30 -0700 (PDT)
+Message-Id: <40900559-DAB1-4956-BA87-F88E00CF5104 at example.com>
+From: xyzzy <xyzzy at example.com>
+To: thisdoesnotexist at example.com
+Content-Type: text/plain; charset=US-ASCII; format=flowed
+Content-Transfer-Encoding: 7bit
+Mime-Version: 1.0 (example Message framework v928.1)
+Subject: testing
+Date: Thu, 7 Aug 2008 16:06:29 -0700
+X-Mailer: example Mail (2.928.1)
+X-Brightmail-Tracker: AAAAAA==
+
+asdf
+
+--4B6C6374C3E5.1218150390/mail-out4.example.com--

Deleted: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_no_original	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original	2008-08-29 05:11:43 UTC (rev 2908)
@@ -1,61 +0,0 @@
-Return-path: <>
-Received: from hemlock.example.com ([17.128.115.180])
- by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
- 14 2008; 32bit)) with ESMTP id <0K5I00K1J5OSXGB0 at mail4.example.com> for
- xyzzy at example.com; Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
-Original-recipient: rfc822;xyzzy at example.com
-Received: from relay11.example.com ([17.128.113.48])
- by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
- 2007)) with ESMTP id <0K5I00KL15OSO0E0 at hemlock.example.com> for
- xyzzy at example.com (ORCPT xyzzy at example.com); Tue,
- 12 Aug 2008 12:19:40 -0700 (PDT)
-Received: by relay11.example.com (Symantec Mail Security)	id 15BA528084; Tue,
- 12 Aug 2008 12:19:40 -0700 (PDT)
-Date: Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
-From: MAILER-DAEMON at relay11.example.com (Mail Delivery System)
-Subject: Undelivered Mail Returned to Sender
-To: xyzzy at example.com
-Message-id: <20080812191940.15BA528084 at relay11.example.com>
-MIME-version: 1.0
-Content-type: multipart/report; report-type=delivery-status;
- boundary="EC6672808D.1218568780/relay11.example.com"
-
-This is a MIME-encapsulated message.
-
---EC6672808D.1218568780/relay11.example.com
-Content-Description: Notification
-Content-Type: text/plain
-
-This is the Symantec Mail Security program at host relay11.example.com.
-
-I'm sorry to have to inform you that your message could not
-be delivered to one or more recipients. It's attached below.
-
-For further assistance, please send mail to <postmaster>
-
-If you do so, please include this problem report. You can
-delete your own text from the attached returned message.
-
-			The Symantec Mail Security program
-
-<nonexistant at example.com>: host elderberry.example.com[17.128.115.181] said: 550
-    5.1.1 unknown or illegal alias: nonexistant at example.com (in reply to RCPT TO
-    command)
-
---EC6672808D.1218568780/relay11.example.com
-Content-Description: Delivery report
-Content-Type: message/delivery-status
-
-Reporting-MTA: dns; relay11.example.com
-X-Symantec-Mail-Security-Queue-ID: EC6672808D
-X-Symantec-Mail-Security-Sender: rfc822; xyzzy at example.com
-Arrival-Date: Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
-
-Final-Recipient: rfc822; nonexistant at example.com
-Action: failed
-Status: 5.0.0
-Diagnostic-Code: X-Symantec-Mail-Security; host
-    elderberry.example.com[17.128.115.181] said: 550 5.1.1 unknown or illegal
-    alias: nonexistant at example.com (in reply to RCPT TO command)
-
---EC6672808D.1218568780/relay11.example.com

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_no_original)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_no_original	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,61 @@
+Return-path: <>
+Received: from hemlock.example.com ([17.128.115.180])
+ by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
+ 14 2008; 32bit)) with ESMTP id <0K5I00K1J5OSXGB0 at mail4.example.com> for
+ xyzzy at example.com; Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
+Original-recipient: rfc822;xyzzy at example.com
+Received: from relay11.example.com ([17.128.113.48])
+ by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
+ 2007)) with ESMTP id <0K5I00KL15OSO0E0 at hemlock.example.com> for
+ xyzzy at example.com (ORCPT xyzzy at example.com); Tue,
+ 12 Aug 2008 12:19:40 -0700 (PDT)
+Received: by relay11.example.com (Symantec Mail Security)	id 15BA528084; Tue,
+ 12 Aug 2008 12:19:40 -0700 (PDT)
+Date: Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
+From: MAILER-DAEMON at relay11.example.com (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+To: xyzzy at example.com
+Message-id: <20080812191940.15BA528084 at relay11.example.com>
+MIME-version: 1.0
+Content-type: multipart/report; report-type=delivery-status;
+ boundary="EC6672808D.1218568780/relay11.example.com"
+
+This is a MIME-encapsulated message.
+
+--EC6672808D.1218568780/relay11.example.com
+Content-Description: Notification
+Content-Type: text/plain
+
+This is the Symantec Mail Security program at host relay11.example.com.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to <postmaster>
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+			The Symantec Mail Security program
+
+<nonexistant at example.com>: host elderberry.example.com[17.128.115.181] said: 550
+    5.1.1 unknown or illegal alias: nonexistant at example.com (in reply to RCPT TO
+    command)
+
+--EC6672808D.1218568780/relay11.example.com
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; relay11.example.com
+X-Symantec-Mail-Security-Queue-ID: EC6672808D
+X-Symantec-Mail-Security-Sender: rfc822; xyzzy at example.com
+Arrival-Date: Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
+
+Final-Recipient: rfc822; nonexistant at example.com
+Action: failed
+Status: 5.0.0
+Diagnostic-Code: X-Symantec-Mail-Security; host
+    elderberry.example.com[17.128.115.181] said: 550 5.1.1 unknown or illegal
+    alias: nonexistant at example.com (in reply to RCPT TO command)
+
+--EC6672808D.1218568780/relay11.example.com

Deleted: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_with_ics	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics	2008-08-29 05:11:43 UTC (rev 2908)
@@ -1,144 +0,0 @@
-Return-path: <>
-Received: from hemlock.example.com ([17.128.115.180])
- by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
- 14 2008; 32bit)) with ESMTP id <0K5I00K1J5OSXGB0 at mail4.example.com> for
- xyzzy at example.com; Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
-Original-recipient: rfc822;xyzzy at example.com
-Received: from relay11.example.com ([17.128.113.48])
- by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
- 2007)) with ESMTP id <0K5I00KL15OSO0E0 at hemlock.example.com> for
- xyzzy at example.com (ORCPT xyzzy at example.com); Tue,
- 12 Aug 2008 12:19:40 -0700 (PDT)
-Received: by relay11.example.com (Symantec Mail Security)	id 15BA528084; Tue,
- 12 Aug 2008 12:19:40 -0700 (PDT)
-Date: Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
-From: MAILER-DAEMON at relay11.example.com (Mail Delivery System)
-Subject: Undelivered Mail Returned to Sender
-To: xyzzy at example.com
-Message-id: <20080812191940.15BA528084 at relay11.example.com>
-MIME-version: 1.0
-Content-type: multipart/report; report-type=delivery-status;
- boundary="EC6672808D.1218568780/relay11.example.com"
-
-This is a MIME-encapsulated message.
-
---EC6672808D.1218568780/relay11.example.com
-Content-Description: Notification
-Content-Type: text/plain
-
-This is the Symantec Mail Security program at host relay11.example.com.
-
-I'm sorry to have to inform you that your message could not
-be delivered to one or more recipients. It's attached below.
-
-For further assistance, please send mail to <postmaster>
-
-If you do so, please include this problem report. You can
-delete your own text from the attached returned message.
-
-			The Symantec Mail Security program
-
-<nonexistant at example.com>: host elderberry.example.com[17.128.115.181] said: 550
-    5.1.1 unknown or illegal alias: nonexistant at example.com (in reply to RCPT TO
-    command)
-
---EC6672808D.1218568780/relay11.example.com
-Content-Description: Delivery report
-Content-Type: message/delivery-status
-
-Reporting-MTA: dns; relay11.example.com
-X-Symantec-Mail-Security-Queue-ID: EC6672808D
-X-Symantec-Mail-Security-Sender: rfc822; xyzzy at example.com
-Arrival-Date: Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
-
-Final-Recipient: rfc822; nonexistant at example.com
-Action: failed
-Status: 5.0.0
-Diagnostic-Code: X-Symantec-Mail-Security; host
-    elderberry.example.com[17.128.115.181] said: 550 5.1.1 unknown or illegal
-    alias: nonexistant at example.com (in reply to RCPT TO command)
-
---EC6672808D.1218568780/relay11.example.com
-Content-Description: Undelivered Message
-Content-Type: message/rfc822
-
-Received: from relay11.example.com (unknown [127.0.0.1])
-	by relay11.example.com (Symantec Mail Security) with ESMTP id EC6672808D
-	for <nonexistant at example.com>; Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
-X-AuditID: 11807130-aa391bb000000ead-f9-48a1e24b510f
-Received: from plugh.example.com (plugh.example.com [17.224.21.17])
-	by relay11.example.com (example SCV relay) with SMTP id D58CA2804F
-	for <nonexistant at example.com>; Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
-From: xyzzy at example.com
-Reply-To: user01 at example.com
-To: nonexistant at example.com
-Date: Tue, 12 Aug 2008 12:19:39 -0700
-Subject: Event invitation: New Event
-Message-ID: <20080812191939.51369.1538816694.0 at plugh.example.com>
-Mime-Version: 1.0
-Content-Type: multipart/mixed;
-    boundary="17.224.21.17.269694933.51369.1218568779.859.1"
-X-Brightmail-Tracker: AAAAAA==
-
-
---17.224.21.17.269694933.51369.1218568779.859.1
-Content-Type: text/plain
-
-You've been invited to the following event:  
-
-Summary: New Event
-Organizer: User 01 <mailto:user01 at example.com>
-Starts:      Tuesday, August 12, 2008 09:45 AM (UTC)
-Ends:        Tuesday, August 12, 2008 10:45 AM (UTC)
-Duration:    1 hour
-Description: 
-
- To accept or decline this invitation, click the link below.
-
---17.224.21.17.269694933.51369.1218568779.859.1
-Content-Type: text/calendar; charset=utf-8
-Content-Transfer-Encoding: 7bit
-
-BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-PRODID:-//example Inc.//iCal 3.0//EN
-BEGIN:VTIMEZONE
-TZID:US/Pacific
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-TZNAME:PST
-TZOFFSETFROM:-0700
-TZOFFSETTO:-0800
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:PDT
-TZOFFSETFROM:-0800
-TZOFFSETTO:-0700
-END:DAYLIGHT
-END:VTIMEZONE
-BEGIN:VEVENT
-UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C
-DTSTART;TZID=US/Pacific:20080812T094500
-DTEND;TZID=US/Pacific:20080812T104500
-ATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam
- ple.com
-ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A
- CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com
-CREATED:20080812T191857Z
-DTSTAMP:20080812T191932Z
-ORGANIZER;CN=User 01:mailto:xyzzy+8e16b897-d544-4217-88e9-a363d08
- 46f6c at example.com
-SEQUENCE:2
-SUMMARY:New Event
-TRANSP:OPAQUE
-END:VEVENT
-END:VCALENDAR
-
---17.224.21.17.269694933.51369.1218568779.859.1--
-
---EC6672808D.1218568780/relay11.example.com--

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/dsn_failure_with_ics)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/dsn_failure_with_ics	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,144 @@
+Return-path: <>
+Received: from hemlock.example.com ([17.128.115.180])
+ by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
+ 14 2008; 32bit)) with ESMTP id <0K5I00K1J5OSXGB0 at mail4.example.com> for
+ xyzzy at example.com; Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
+Original-recipient: rfc822;xyzzy at example.com
+Received: from relay11.example.com ([17.128.113.48])
+ by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
+ 2007)) with ESMTP id <0K5I00KL15OSO0E0 at hemlock.example.com> for
+ xyzzy at example.com (ORCPT xyzzy at example.com); Tue,
+ 12 Aug 2008 12:19:40 -0700 (PDT)
+Received: by relay11.example.com (Symantec Mail Security)	id 15BA528084; Tue,
+ 12 Aug 2008 12:19:40 -0700 (PDT)
+Date: Tue, 12 Aug 2008 12:19:40 -0700 (PDT)
+From: MAILER-DAEMON at relay11.example.com (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+To: xyzzy at example.com
+Message-id: <20080812191940.15BA528084 at relay11.example.com>
+MIME-version: 1.0
+Content-type: multipart/report; report-type=delivery-status;
+ boundary="EC6672808D.1218568780/relay11.example.com"
+
+This is a MIME-encapsulated message.
+
+--EC6672808D.1218568780/relay11.example.com
+Content-Description: Notification
+Content-Type: text/plain
+
+This is the Symantec Mail Security program at host relay11.example.com.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to <postmaster>
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+			The Symantec Mail Security program
+
+<nonexistant at example.com>: host elderberry.example.com[17.128.115.181] said: 550
+    5.1.1 unknown or illegal alias: nonexistant at example.com (in reply to RCPT TO
+    command)
+
+--EC6672808D.1218568780/relay11.example.com
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; relay11.example.com
+X-Symantec-Mail-Security-Queue-ID: EC6672808D
+X-Symantec-Mail-Security-Sender: rfc822; xyzzy at example.com
+Arrival-Date: Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
+
+Final-Recipient: rfc822; nonexistant at example.com
+Action: failed
+Status: 5.0.0
+Diagnostic-Code: X-Symantec-Mail-Security; host
+    elderberry.example.com[17.128.115.181] said: 550 5.1.1 unknown or illegal
+    alias: nonexistant at example.com (in reply to RCPT TO command)
+
+--EC6672808D.1218568780/relay11.example.com
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Received: from relay11.example.com (unknown [127.0.0.1])
+	by relay11.example.com (Symantec Mail Security) with ESMTP id EC6672808D
+	for <nonexistant at example.com>; Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
+X-AuditID: 11807130-aa391bb000000ead-f9-48a1e24b510f
+Received: from plugh.example.com (plugh.example.com [17.224.21.17])
+	by relay11.example.com (example SCV relay) with SMTP id D58CA2804F
+	for <nonexistant at example.com>; Tue, 12 Aug 2008 12:19:39 -0700 (PDT)
+From: xyzzy at example.com
+Reply-To: user01 at example.com
+To: nonexistant at example.com
+Date: Tue, 12 Aug 2008 12:19:39 -0700
+Subject: Event invitation: New Event
+Message-ID: <20080812191939.51369.1538816694.0 at plugh.example.com>
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+    boundary="17.224.21.17.269694933.51369.1218568779.859.1"
+X-Brightmail-Tracker: AAAAAA==
+
+
+--17.224.21.17.269694933.51369.1218568779.859.1
+Content-Type: text/plain
+
+You've been invited to the following event:  
+
+Summary: New Event
+Organizer: User 01 <mailto:user01 at example.com>
+Starts:      Tuesday, August 12, 2008 09:45 AM (UTC)
+Ends:        Tuesday, August 12, 2008 10:45 AM (UTC)
+Duration:    1 hour
+Description: 
+
+ To accept or decline this invitation, click the link below.
+
+--17.224.21.17.269694933.51369.1218568779.859.1
+Content-Type: text/calendar; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//example Inc.//iCal 3.0//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C
+DTSTART;TZID=US/Pacific:20080812T094500
+DTEND;TZID=US/Pacific:20080812T104500
+ATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam
+ ple.com
+ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A
+ CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com
+CREATED:20080812T191857Z
+DTSTAMP:20080812T191932Z
+ORGANIZER;CN=User 01:mailto:xyzzy+8e16b897-d544-4217-88e9-a363d08
+ 46f6c at example.com
+SEQUENCE:2
+SUMMARY:New Event
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+
+--17.224.21.17.269694933.51369.1218568779.859.1--
+
+--EC6672808D.1218568780/relay11.example.com--

Deleted: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/good_reply	2008-08-29 04:45:04 UTC (rev 2907)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply	2008-08-29 05:11:43 UTC (rev 2908)
@@ -1,92 +0,0 @@
-Return-path: <xyzzy at example.com>
-Received: from hemlock.example.com ([17.128.115.180])
- by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
- 14 2008; 32bit)) with ESMTP id <0K5I00I8I8G50QC0 at mail4.example.com> for
- ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com; Tue,
- 12 Aug 2008 13:19:17 -0700 (PDT)
-Original-recipient:
- rfc822;ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com
-Received: from relay14.example.com ([17.128.113.52])
- by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
- 2007)) with ESMTP id <0K5I004UB8G5UR90 at hemlock.example.com> for
- ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com
- (ORCPT ical-living-on+2Bd7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com); Tue,
- 12 Aug 2008 13:19:17 -0700 (PDT)
-Received: from relay14.example.com (unknown [127.0.0.1])
-	by relay14.example.com (Symantec Mail Security) with ESMTP id 6A5EF28087	for
- <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>; Tue,
- 12 Aug 2008 13:19:17 -0700 (PDT)
-Received: from [17.83.208.154] (unknown [17.83.208.154])
-	(using TLSv1 with cipher AES128-SHA (128/128 bits))
-	(No client certificate requested)	by relay14.example.com (example SCV relay)
- with ESMTP id CAEE22808B	for
- <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>; Tue,
- 12 Aug 2008 13:19:16 -0700 (PDT)
-Date: Tue, 12 Aug 2008 13:19:14 -0700
-From: plugh xyzzy <xyzzy at example.com>
-Subject: Event accepted: New Event
-To: User 01 <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>
-Message-id: <1983F777-BE86-4B98-881E-06D938E60920 at example.com>
-MIME-version: 1.0 (example Message framework v928.1)
-X-Mailer: example Mail (2.928.1)
-Content-type: multipart/alternative; boundary=example-Mail-1--253014167
-X-Mail-Calendar-Part: Yes
-X-Brightmail-Tracker: AAAAAA==
-
-
---example-Mail-1--253014167
-Content-Type: text/plain;
-	charset=US-ASCII;
-	format=flowed;
-	delsp=yes
-Content-Transfer-Encoding: 7bit
-
-plugh xyzzy has accepted your iCal event invitation to the event: New  
-Event, scheduled for August 12, 2008 at 10:00 AM (US/Pacific).
---example-Mail-1--253014167
-Content-Type: multipart/mixed;
-	boundary=example-Mail-2--253014167
-
-
---example-Mail-2--253014167
-Content-Type: text/html;
-	charset=US-ASCII
-Content-Transfer-Encoding: 7bit
-
-<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><font face="Helvetica" size="3" style="font: 12.0px Helvetica">plugh xyzzy has accepted your iCal event invitation to the event: New Event, scheduled for August 12, 2008 at 10:00 AM (US/Pacific).</font></div></div></body></html>
---example-Mail-2--253014167
-Content-Disposition: attachment;
-	filename=iCal-20080812-131911.ics
-Content-Type: text/calendar;
-	x-unix-mode=0644;
-	name="iCal-20080812-131911.ics"
-Content-Transfer-Encoding: quoted-printable
-
-BEGIN:VCALENDAR=0D=0APRODID:-//example=20Inc.//iCal=203.0//EN=0D=0A=
-CALSCALE:GREGORIAN=0D=0AVERSION:2.0=0D=0AMETHOD:REPLY=0D=0A=
-BEGIN:VTIMEZONE=0D=0ATZID:US/Pacific=0D=0ABEGIN:DAYLIGHT=0D=0A=
-TZOFFSETFROM:-0800=0D=0ATZOFFSETTO:-0700=0D=0ADTSTART:20070311T020000=0D=0A=
-RRULE:FREQ=3DYEARLY;BYMONTH=3D3;BYDAY=3D2SU=0D=0ATZNAME:PDT=0D=0A=
-END:DAYLIGHT=0D=0ABEGIN:STANDARD=0D=0ATZOFFSETFROM:-0700=0D=0A=
-TZOFFSETTO:-0800=0D=0ADTSTART:20071104T020000=0D=0A=
-RRULE:FREQ=3DYEARLY;BYMONTH=3D11;BYDAY=3D1SU=0D=0ATZNAME:PST=0D=0A=
-END:STANDARD=0D=0AEND:VTIMEZONE=0D=0ABEGIN:VEVENT=0D=0ASEQUENCE:7=0D=0A=
-DTSTART;TZID=3DUS/Pacific:20080812T100000=0D=0A=
-UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C=0D=0AORGANIZER;CN=3D"User=20=
-01":mailto:ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08=0D=0A=20=
-002fb285f at example.com=0D=0ADTSTAMP:20080812T201911Z=0D=0ASUMMARY:New=20=
-Event=0D=0A=
-ATTENDEE;CN=3D"xyzzy at example.com";CUTYPE=3DINDIVIDUAL;PARTSTAT=3DACCEPTED;RO=
-LE=3DR=0D=0A=20EQ-PARTICIPANT:mailto:xyzzy at example.com=0D=0A=
-CREATED:20080812T201906Z=0D=0ADTEND;TZID=3DUS/Pacific:20080812T110000=0D=0A=
-END:VEVENT=0D=0AEND:VCALENDAR=0D=0A=
-
---example-Mail-2--253014167
-Content-Type: text/html;
-	charset=US-ASCII
-Content-Transfer-Encoding: 7bit
-
-<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "></div></div></body></html>
---example-Mail-2--253014167--
-
---example-Mail-1--253014167--

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/data/mail/good_reply)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/data/mail/good_reply	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,92 @@
+Return-path: <xyzzy at example.com>
+Received: from hemlock.example.com ([17.128.115.180])
+ by mail4.example.com (Sun Java(tm) System Messaging Server 6.3-6.03 (built Mar
+ 14 2008; 32bit)) with ESMTP id <0K5I00I8I8G50QC0 at mail4.example.com> for
+ ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com; Tue,
+ 12 Aug 2008 13:19:17 -0700 (PDT)
+Original-recipient:
+ rfc822;ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com
+Received: from relay14.example.com ([17.128.113.52])
+ by hemlock.example.com (Sun Java System Messaging Server 6.2-8.04 (built Feb 28
+ 2007)) with ESMTP id <0K5I004UB8G5UR90 at hemlock.example.com> for
+ ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com
+ (ORCPT ical-living-on+2Bd7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com); Tue,
+ 12 Aug 2008 13:19:17 -0700 (PDT)
+Received: from relay14.example.com (unknown [127.0.0.1])
+	by relay14.example.com (Symantec Mail Security) with ESMTP id 6A5EF28087	for
+ <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>; Tue,
+ 12 Aug 2008 13:19:17 -0700 (PDT)
+Received: from [17.83.208.154] (unknown [17.83.208.154])
+	(using TLSv1 with cipher AES128-SHA (128/128 bits))
+	(No client certificate requested)	by relay14.example.com (example SCV relay)
+ with ESMTP id CAEE22808B	for
+ <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>; Tue,
+ 12 Aug 2008 13:19:16 -0700 (PDT)
+Date: Tue, 12 Aug 2008 13:19:14 -0700
+From: plugh xyzzy <xyzzy at example.com>
+Subject: Event accepted: New Event
+To: User 01 <ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08002fb285f at example.com>
+Message-id: <1983F777-BE86-4B98-881E-06D938E60920 at example.com>
+MIME-version: 1.0 (example Message framework v928.1)
+X-Mailer: example Mail (2.928.1)
+Content-type: multipart/alternative; boundary=example-Mail-1--253014167
+X-Mail-Calendar-Part: Yes
+X-Brightmail-Tracker: AAAAAA==
+
+
+--example-Mail-1--253014167
+Content-Type: text/plain;
+	charset=US-ASCII;
+	format=flowed;
+	delsp=yes
+Content-Transfer-Encoding: 7bit
+
+plugh xyzzy has accepted your iCal event invitation to the event: New  
+Event, scheduled for August 12, 2008 at 10:00 AM (US/Pacific).
+--example-Mail-1--253014167
+Content-Type: multipart/mixed;
+	boundary=example-Mail-2--253014167
+
+
+--example-Mail-2--253014167
+Content-Type: text/html;
+	charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+
+<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><font face="Helvetica" size="3" style="font: 12.0px Helvetica">plugh xyzzy has accepted your iCal event invitation to the event: New Event, scheduled for August 12, 2008 at 10:00 AM (US/Pacific).</font></div></div></body></html>
+--example-Mail-2--253014167
+Content-Disposition: attachment;
+	filename=iCal-20080812-131911.ics
+Content-Type: text/calendar;
+	x-unix-mode=0644;
+	name="iCal-20080812-131911.ics"
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR=0D=0APRODID:-//example=20Inc.//iCal=203.0//EN=0D=0A=
+CALSCALE:GREGORIAN=0D=0AVERSION:2.0=0D=0AMETHOD:REPLY=0D=0A=
+BEGIN:VTIMEZONE=0D=0ATZID:US/Pacific=0D=0ABEGIN:DAYLIGHT=0D=0A=
+TZOFFSETFROM:-0800=0D=0ATZOFFSETTO:-0700=0D=0ADTSTART:20070311T020000=0D=0A=
+RRULE:FREQ=3DYEARLY;BYMONTH=3D3;BYDAY=3D2SU=0D=0ATZNAME:PDT=0D=0A=
+END:DAYLIGHT=0D=0ABEGIN:STANDARD=0D=0ATZOFFSETFROM:-0700=0D=0A=
+TZOFFSETTO:-0800=0D=0ADTSTART:20071104T020000=0D=0A=
+RRULE:FREQ=3DYEARLY;BYMONTH=3D11;BYDAY=3D1SU=0D=0ATZNAME:PST=0D=0A=
+END:STANDARD=0D=0AEND:VTIMEZONE=0D=0ABEGIN:VEVENT=0D=0ASEQUENCE:7=0D=0A=
+DTSTART;TZID=3DUS/Pacific:20080812T100000=0D=0A=
+UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C=0D=0AORGANIZER;CN=3D"User=20=
+01":mailto:ical-living-on+d7cdf68d-8b73-4df1-ad3b-f08=0D=0A=20=
+002fb285f at example.com=0D=0ADTSTAMP:20080812T201911Z=0D=0ASUMMARY:New=20=
+Event=0D=0A=
+ATTENDEE;CN=3D"xyzzy at example.com";CUTYPE=3DINDIVIDUAL;PARTSTAT=3DACCEPTED;RO=
+LE=3DR=0D=0A=20EQ-PARTICIPANT:mailto:xyzzy at example.com=0D=0A=
+CREATED:20080812T201906Z=0D=0ADTEND;TZID=3DUS/Pacific:20080812T110000=0D=0A=
+END:VEVENT=0D=0AEND:VCALENDAR=0D=0A=
+
+--example-Mail-2--253014167
+Content-Type: text/html;
+	charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+
+<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "></div></div></body></html>
+--example-Mail-2--253014167--
+
+--example-Mail-1--253014167--

Copied: CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/test_mail.py (from rev 2907, CalendarServer/branches/users/sagen/mailgateway-2881/twistedcaldav/test/test_mail.py)
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/test_mail.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/mailgateway-2906/twistedcaldav/test/test_mail.py	2008-08-29 05:11:43 UTC (rev 2908)
@@ -0,0 +1,105 @@
+##
+# Copyright (c) 2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.task import Clock
+from twistedcaldav.mail import *
+from twistedcaldav import config as config_mod
+from twistedcaldav.config import Config
+import email
+import os
+
+
+def echo(*args):
+    return args
+
+
+class MailHandlerTests(TestCase):
+
+    def setUp(self):
+        self.handler = MailHandler(dataRoot=":memory:")
+        self.dataDir = os.path.join(os.path.dirname(__file__), "data", "mail")
+
+    def test_checkDSNFailure(self):
+
+        data = {
+            'good_reply' : (False, None, None),
+            'dsn_failure_no_original' : (True, 'failed', None),
+            'dsn_failure_no_ics' : (True, 'failed', None),
+            'dsn_failure_with_ics' : (True, 'failed', 'BEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nMETHOD:REQUEST\nPRODID:-//example Inc.//iCal 3.0//EN\nBEGIN:VTIMEZONE\nTZID:US/Pacific\nBEGIN:STANDARD\nDTSTART:20071104T020000\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\nTZNAME:PST\nTZOFFSETFROM:-0700\nTZOFFSETTO:-0800\nEND:STANDARD\nBEGIN:DAYLIGHT\nDTSTART:20070311T020000\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\nTZNAME:PDT\nTZOFFSETFROM:-0800\nTZOFFSETTO:-0700\nEND:DAYLIGHT\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C\nDTSTART;TZID=US/Pacific:20080812T094500\nDTEND;TZID=US/Pacific:20080812T104500\nATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam\n ple.com\nATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A\n CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com\nCREATED:20080812T191857Z\nDTSTAMP:20080812T191932Z\nORGANIZER;CN=User 01:mailto:xyzzy+8e16b897-d544-4217-88e9-a363d08\n 46f6c at ex
 ample.com\nSEQUENCE:2\nSUMMARY:New Event\nTRANSP:OPAQUE\nEND:VEVENT\nEND:VCALENDAR\n'),
+        }
+
+        for filename, expected in data.iteritems():
+            msg = email.message_from_string(
+                file(os.path.join(self.dataDir, filename)).read()
+            )
+            self.assertEquals(self.handler.checkDSN(msg), expected)
+
+
+    def test_processDSN(self):
+
+        template = 'BEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nMETHOD:REQUEST\nPRODID:-//example Inc.//iCal 3.0//EN\nBEGIN:VTIMEZONE\nTZID:US/Pacific\nBEGIN:STANDARD\nDTSTART:20071104T020000\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\nTZNAME:PST\nTZOFFSETFROM:-0700\nTZOFFSETTO:-0800\nEND:STANDARD\nBEGIN:DAYLIGHT\nDTSTART:20070311T020000\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\nTZNAME:PDT\nTZOFFSETFROM:-0800\nTZOFFSETTO:-0700\nEND:DAYLIGHT\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C\nDTSTART;TZID=US/Pacific:20080812T094500\nDTEND;TZID=US/Pacific:20080812T104500\nATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01 at exam\n ple.com\nATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A\n CTION;CN=nonexistant at example.com:mailto:nonexistant at example.com\nCREATED:20080812T191857Z\nDTSTAMP:20080812T191932Z\nORGANIZER;CN=User 01:mailto:xyzzy+%s at example.com\nSEQUENCE:2\nSUMMARY:New Event\nTRANSP:OPAQUE\nEND:VEVENT\nEND
 :VCALENDAR\n'
+
+        # Make sure an unknown token is not processed
+        calBody = template % "bogus_token"
+        self.assertEquals(self.handler.processDSN(calBody, "xyzzy", echo),
+           None)
+
+        # Make sure a known token *is* processed
+        token = self.handler.db.createToken("mailto:user01 at example.com",
+            "mailto:user02 at example.com")
+        calBody = template % token
+        organizer, attendee, calendar, msgId = self.handler.processDSN(calBody,
+            "xyzzy", echo)
+        self.assertEquals(organizer, 'mailto:user01 at example.com')
+        self.assertEquals(attendee, 'mailto:user02 at example.com')
+        self.assertEquals(str(calendar), 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//example Inc.//iCal 3.0//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C\r\nDTSTART;TZID=US/Pacific:20080812T094500\r\nDTEND;TZID=US/Pacific:20080812T104500\r\nCREATED:20080812T191857Z\r\nDTSTAMP:20080812T191932Z\r\nORGANIZER;CN=User 01:mailto:user01 at example.com\r\nREQUEST-STATUS:5.1;Service unavailable\r\nSEQUENCE:2\r\nSUMMARY:New Event\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')
+        self.assertEquals(msgId, 'xyzzy')
+
+
+
+    def test_processReply(self):
+        msg = email.message_from_string(
+            file(os.path.join(self.dataDir, 'good_reply')).read()
+        )
+
+        # Make sure an unknown token is not processed
+        result = self.handler.processReply(msg, echo)
+        self.assertEquals(result, None)
+
+        # Make sure a known token *is* processed
+        self.handler.db.createToken("mailto:user01 at example.com", "mailto:xyzzy at example.com", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f")
+        organizer, attendee, calendar, msgId = self.handler.processReply(msg,
+            echo)
+        self.assertEquals(organizer, 'mailto:user01 at example.com')
+        self.assertEquals(attendee, 'mailto:xyzzy at example.com')
+        self.assertEquals(msgId, '<1983F777-BE86-4B98-881E-06D938E60920 at example.com>')
+
+
+class MailGatewayTokensDatabaseTests(TestCase):
+
+    def setUp(self):
+        self.db = MailGatewayTokensDatabase(":memory:")
+
+    def test_tokens(self):
+        self.assertEquals(self.db.lookupByToken("xyzzy"), None)
+
+        token = self.db.createToken("organizer", "attendee")
+        self.assertEquals(self.db.getToken("organizer", "attendee"), token)
+        self.assertEquals(self.db.lookupByToken(token),
+            ("organizer", "attendee"))
+        self.db.deleteToken(token)
+        self.assertEquals(self.db.lookupByToken(token), None)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080828/e64a65f9/attachment-0001.html 


More information about the calendarserver-changes mailing list