[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