[CalendarServer-changes] [3881] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 18 09:11:57 PDT 2009


Revision: 3881
          http://trac.macosforge.org/projects/calendarserver/changeset/3881
Author:   sagen at apple.com
Date:     2009-03-18 09:11:56 -0700 (Wed, 18 Mar 2009)
Log Message:
-----------
Authenticate when POSTing iMIP replies to calendar server's /inbox

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/config.py
    CalendarServer/trunk/twistedcaldav/mail.py

Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py	2009-03-17 23:46:17 UTC (rev 3880)
+++ CalendarServer/trunk/twistedcaldav/config.py	2009-03-18 16:11:56 UTC (rev 3881)
@@ -678,6 +678,12 @@
         service = self.Scheduling["iMIP"]
 
         if service["Enabled"]:
+
+            # Get password for the user that is allowed to inject iMIP replies
+            # to the server's /inbox
+            if service.Username:
+                service.Password = getPasswordFromKeychain(service.Username)
+
             for direction in ("Sending", "Receiving"):
                 if service[direction].Username:
                     # Get password from keychain.  If not there, fall back to

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2009-03-17 23:46:17 UTC (rev 3880)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2009-03-18 16:11:56 UTC (rev 3881)
@@ -26,11 +26,12 @@
 
 from twisted.application import internet, service
 from twisted.internet import protocol, defer, ssl, reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed, fail
 from twisted.mail import pop3client, imap4
 from twisted.mail.smtp import messageid, rfc822date, ESMTPSenderFactory
 from twisted.plugin import IPlugin
 from twisted.python.usage import Options, UsageError
+from twisted.python import failure
 from twisted.web import resource, server, client
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
@@ -55,6 +56,9 @@
 import email.utils
 import os
 import uuid
+from hashlib import md5, sha1
+import base64
+import time
 
 try:
     from cStringIO import StringIO
@@ -266,6 +270,226 @@
         return succeed(deliverSchedulePrivilegeSet)
 
 
+
+
+algorithms = {
+    'md5': md5,
+    'md5-sess': md5,
+    'sha': sha1,
+}
+
+# DigestCalcHA1
+def calcHA1(
+    pszAlg,
+    pszUserName,
+    pszRealm,
+    pszPassword,
+    pszNonce,
+    pszCNonce,
+    preHA1=None
+):
+    """
+    @param pszAlg: The name of the algorithm to use to calculate the digest.
+        Currently supported are md5 md5-sess and sha.
+
+    @param pszUserName: The username
+    @param pszRealm: The realm
+    @param pszPassword: The password
+    @param pszNonce: The nonce
+    @param pszCNonce: The cnonce
+
+    @param preHA1: If available this is a str containing a previously
+       calculated HA1 as a hex string. If this is given then the values for
+       pszUserName, pszRealm, and pszPassword are ignored.
+    """
+
+    if (preHA1 and (pszUserName or pszRealm or pszPassword)):
+        raise TypeError(("preHA1 is incompatible with the pszUserName, "
+                         "pszRealm, and pszPassword arguments"))
+
+    if preHA1 is None:
+        # We need to calculate the HA1 from the username:realm:password
+        m = algorithms[pszAlg]()
+        m.update(pszUserName)
+        m.update(":")
+        m.update(pszRealm)
+        m.update(":")
+        m.update(pszPassword)
+        HA1 = m.digest()
+    else:
+        # We were given a username:realm:password
+        HA1 = preHA1.decode('hex')
+
+    if pszAlg == "md5-sess":
+        m = algorithms[pszAlg]()
+        m.update(HA1)
+        m.update(":")
+        m.update(pszNonce)
+        m.update(":")
+        m.update(pszCNonce)
+        HA1 = m.digest()
+
+    return HA1.encode('hex')
+
+# DigestCalcResponse
+def calcResponse(
+    HA1,
+    algo,
+    pszNonce,
+    pszNonceCount,
+    pszCNonce,
+    pszQop,
+    pszMethod,
+    pszDigestUri,
+    pszHEntity,
+):
+    m = algorithms[algo]()
+    m.update(pszMethod)
+    m.update(":")
+    m.update(pszDigestUri)
+    if pszQop == "auth-int":
+        m.update(":")
+        m.update(pszHEntity)
+    HA2 = m.digest().encode('hex')
+
+    m = algorithms[algo]()
+    m.update(HA1)
+    m.update(":")
+    m.update(pszNonce)
+    m.update(":")
+    if pszNonceCount and pszCNonce and pszQop:
+        m.update(pszNonceCount)
+        m.update(":")
+        m.update(pszCNonce)
+        m.update(":")
+        m.update(pszQop)
+        m.update(":")
+    m.update(HA2)
+    respHash = m.digest().encode('hex')
+    return respHash
+
+class Unauthorized(Exception):
+    pass
+
+class AuthorizedHTTPGetter(client.HTTPPageGetter, LoggingMixIn):
+
+    def handleStatus_401(self):
+
+        self.quietLoss = 1
+        self.transport.loseConnection()
+
+        if hasattr(self.factory, "retried"):
+            self.factory.deferred.errback(failure.Failure(Unauthorized("Mail gateway not able to process reply; could not authenticate user %s with calendar server" % (self.factory.username,))))
+            return self.factory.deferred
+
+        self.factory.retried = True
+
+        # self.log_debug("Got a 401 trying to inject [%s]" % (self.headers,))
+        details = {}
+        basicAvailable = digestAvailable = False
+        wwwauth = self.headers.get("www-authenticate")
+        for item in wwwauth:
+            if item.startswith("basic "):
+                basicAvailable = True
+            if item.startswith("digest "):
+                digestAvailable = True
+                wwwauth = item[7:]
+                def unq(s):
+                    if s[0] == s[-1] == '"':
+                        return s[1:-1]
+                    return s
+                parts = wwwauth.split(',')
+                for (k, v) in [p.split('=', 1) for p in parts]:
+                    details[k.strip()] = unq(v.strip())
+
+        user = self.factory.username
+        pswd = self.factory.password
+
+        if digestAvailable and details:
+            digest = calcResponse(
+                calcHA1(
+                    details.get('algorithm'),
+                    user,
+                    details.get('realm'),
+                    pswd,
+                    details.get('nonce'),
+                    details.get('cnonce')
+                ),
+                details.get('algorithm'),
+                details.get('nonce'),
+                details.get('nc'),
+                details.get('cnonce'),
+                details.get('qop'),
+                self.factory.method,
+                self.factory.url,
+                None
+            )
+
+            if details.get('qop'):
+                response = (
+                    'Digest username="%s", realm="%s", nonce="%s", uri="%s", '
+                    'response=%s, algorithm=%s, cnonce="%s", qop=%s, nc=%s' %
+                    (
+                        user,
+                        details.get('realm'),
+                        details.get('nonce'),
+                        self.factory.url,
+                        digest,
+                        details.get('algorithm'),
+                        details.get('cnonce'),
+                        details.get('qop'),
+                        details.get('nc'),
+                    )
+                )
+            else:
+                response = (
+                    'Digest username="%s", realm="%s", nonce="%s", uri="%s", '
+                    'response=%s, algorithm=%s' %
+                    (
+                        user,
+                        details.get('realm'),
+                        details.get('nonce'),
+                        self.factory.url,
+                        digest,
+                        details.get('algorithm'),
+                    )
+                )
+
+            self.factory.headers['Authorization'] = response
+
+            if self.factory.scheme == 'https':
+                reactor.connectSSL(self.factory.host, self.factory.port,
+                    self.factory, ssl.ClientContextFactory())
+            else:
+                reactor.connectTCP(self.factory.host, self.factory.port,
+                    self.factory)
+            # self.log_debug("Retrying with digest after 401")
+
+            return self.factory.deferred
+
+        elif basicAvailable:
+            basicauth = "%s:%s" % (user, pswd)
+            basicauth = "Basic " + base64.encodestring( basicauth )
+            basicauth = basicauth.replace( "\n", "" )
+
+            self.factory.headers['Authorization'] = basicauth
+
+            if self.factory.scheme == 'https':
+                reactor.connectSSL(self.factory.host, self.factory.port,
+                    self.factory, ssl.ClientContextFactory())
+            else:
+                reactor.connectTCP(self.factory.host, self.factory.port,
+                    self.factory)
+            # self.log_debug("Retrying with basic after 401")
+
+            return self.factory.deferred
+
+
+        else:
+            self.factory.deferred.errback(failure.Failure(Unauthorized("Mail gateway not able to process reply; calendar server returned 401 and doesn't support basic or digest")))
+            return self.factory.deferred
+
+
 def injectMessage(organizer, attendee, calendar, msgId, reactor=None):
 
     if reactor is None:
@@ -296,9 +520,16 @@
     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, agent="iMIP gateway")
+
+    if config.Scheduling.iMIP.Username:
+        factory.username = config.Scheduling.iMIP.Username
+        factory.password = config.Scheduling.iMIP.Password
+
     factory.noisy = False
+    factory.protocol = AuthorizedHTTPGetter
 
     if useSSL:
         reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090318/5eac0b24/attachment.html>


More information about the calendarserver-changes mailing list