[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