1 # -*- test-case-name: twisted.test.test_sip -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
7 """Session Initialization Protocol.
9 Documented in RFC 2543.
13 This module contains a deprecated implementation of HTTP Digest authentication.
14 See L{twisted.cred.credentials} and L{twisted.cred._digest} for its new home.
18 import socket, time, sys, random, warnings
19 from zope.interface import implements, Interface
22 from twisted.python import log, util
23 from twisted.python.deprecate import deprecated
24 from twisted.python.versions import Version
25 from twisted.python.hashlib import md5
26 from twisted.internet import protocol, defer, reactor
28 from twisted import cred
29 import twisted.cred.error
30 from twisted.cred.credentials import UsernameHashedPassword, UsernamePassword
34 from twisted.protocols import basic
38 # SIP headers have short forms
39 shortHeaders = {"call-id": "i",
41 "content-encoding": "e",
42 "content-length": "l",
51 for k, v in shortHeaders.items():
58 181: "Call Is Being Forwarded",
60 183: "Session Progress",
64 300: "Multiple Choices",
65 301: "Moved Permanently",
66 302: "Moved Temporarily",
69 380: "Alternative Service",
73 402: "Payment Required",
76 405: "Method Not Allowed",
77 406: "Not Acceptable",
78 407: "Proxy Authentication Required",
79 408: "Request Timeout",
80 409: "Conflict", # Not in RFC3261
82 411: "Length Required", # Not in RFC3261
83 413: "Request Entity Too Large",
84 414: "Request-URI Too Large",
85 415: "Unsupported Media Type",
86 416: "Unsupported URI Scheme",
88 421: "Extension Required",
89 423: "Interval Too Brief",
90 480: "Temporarily Unavailable",
91 481: "Call/Transaction Does Not Exist",
94 484: "Address Incomplete",
97 487: "Request Terminated",
98 488: "Not Acceptable Here",
99 491: "Request Pending",
100 493: "Undecipherable",
102 500: "Internal Server Error",
103 501: "Not Implemented",
104 502: "Bad Gateway", # no donut
105 503: "Service Unavailable",
106 504: "Server Time-out",
107 505: "SIP Version not supported",
108 513: "Message Too Large",
110 600: "Busy Everywhere",
112 604: "Does not exist anywhere",
113 606: "Not Acceptable",
118 'call-id': 'Call-ID',
119 'www-authenticate': 'WWW-Authenticate',
123 def dashCapitalize(s):
124 ''' Capitalize a string, making sure to treat - as a word seperator '''
125 return '-'.join([ x.capitalize() for x in s.split('-')])
128 if s[0] == s[-1] == '"':
141 m.update(pszUserName)
145 m.update(pszPassword)
147 if pszAlg == "md5-sess":
155 return HA1.encode('hex')
158 DigestCalcHA1 = deprecated(Version("Twisted", 9, 0, 0))(DigestCalcHA1)
160 def DigestCalcResponse(
173 m.update(pszDigestUri)
174 if pszQop == "auth-int":
177 HA2 = m.digest().encode('hex')
184 if pszNonceCount and pszCNonce: # pszQop:
185 m.update(pszNonceCount)
192 hash = m.digest().encode('hex')
196 DigestCalcResponse = deprecated(Version("Twisted", 9, 0, 0))(DigestCalcResponse)
202 A L{Via} is a SIP Via header, representing a segment of the path taken by
205 See RFC 3261, sections 8.1.1.7, 18.2.2, and 20.42.
207 @ivar transport: Network protocol used for this leg. (Probably either "TCP"
209 @type transport: C{str}
210 @ivar branch: Unique identifier for this request.
212 @ivar host: Hostname or IP for this leg.
214 @ivar port: Port used for this leg.
215 @type port C{int}, or None.
216 @ivar rportRequested: Whether to request RFC 3581 client processing or not.
217 @type rportRequested: C{bool}
218 @ivar rportValue: Servers wishing to honor requests for RFC 3581 processing
219 should set this parameter to the source port the request was received
221 @type rportValue: C{int}, or None.
223 @ivar ttl: Time-to-live for requests on multicast paths.
224 @type ttl: C{int}, or None.
225 @ivar maddr: The destination multicast address, if any.
226 @type maddr: C{str}, or None.
227 @ivar hidden: Obsolete in SIP 2.0.
228 @type hidden: C{bool}
229 @ivar otherParams: Any other parameters in the header.
230 @type otherParams: C{dict}
233 def __init__(self, host, port=PORT, transport="UDP", ttl=None,
234 hidden=False, received=None, rport=_absent, branch=None,
237 Set parameters of this Via header. All arguments correspond to
238 attributes of the same name.
240 To maintain compatibility with old SIP
241 code, the 'rport' argument is used to determine the values of
242 C{rportRequested} and C{rportValue}. If None, C{rportRequested} is set
243 to True. (The deprecated method for doing this is to pass True.) If an
244 integer, C{rportValue} is set to the given value.
246 Any arguments not explicitly named here are collected into the
249 self.transport = transport
254 self.received = received
257 "rport=True is deprecated since Twisted 9.0.",
260 self.rportValue = None
261 self.rportRequested = True
263 self.rportValue = None
264 self.rportRequested = True
265 elif rport is _absent:
266 self.rportValue = None
267 self.rportRequested = False
269 self.rportValue = rport
270 self.rportRequested = False
274 self.otherParams = kw
279 Returns the rport value expected by the old SIP code.
281 if self.rportRequested == True:
283 elif self.rportValue is not None:
284 return self.rportValue
289 def _setrport(self, newRPort):
291 L{Base._fixupNAT} sets C{rport} directly, so this method sets
292 C{rportValue} based on that.
294 @param newRPort: The new rport value.
295 @type newRPort: C{int}
297 self.rportValue = newRPort
298 self.rportRequested = False
301 rport = property(_getrport, _setrport)
305 Serialize this header for use in a request or response.
307 s = "SIP/2.0/%s %s:%s" % (self.transport, self.host, self.port)
310 for n in "ttl", "branch", "maddr", "received":
311 value = getattr(self, n)
312 if value is not None:
313 s += ";%s=%s" % (n, value)
314 if self.rportRequested:
316 elif self.rportValue is not None:
317 s += ";rport=%s" % (self.rport,)
319 etc = self.otherParams.items()
325 s += ";%s=%s" % (k, v)
329 def parseViaHeader(value):
333 @return: The parsed version of this header.
336 parts = value.split(";")
337 sent, params = parts[0], parts[1:]
338 protocolinfo, by = sent.split(" ", 1)
341 pname, pversion, transport = protocolinfo.split("/")
342 if pname != "SIP" or pversion != "2.0":
343 raise ValueError, "wrong protocol or version: %r" % value
344 result["transport"] = transport
346 host, port = by.split(":")
347 result["port"] = int(port)
348 result["host"] = host
352 # it's the comment-striping dance!
353 p = p.strip().split(" ", 1)
355 p, comment = p[0], ""
359 result["hidden"] = True
361 parts = p.split("=", 1)
363 name, value = parts[0], None
366 if name in ("rport", "ttl"):
375 def __init__(self, host, username=None, password=None, port=None,
376 transport=None, usertype=None, method=None,
377 ttl=None, maddr=None, tag=None, other=None, headers=None):
378 self.username = username
380 self.password = password
382 self.transport = transport
383 self.usertype = usertype
395 self.headers = headers
400 if self.username != None:
402 if self.password != None:
403 w(":%s" % self.password)
406 if self.port != None:
408 if self.usertype != None:
409 w(";user=%s" % self.usertype)
410 for n in ("transport", "ttl", "maddr", "method", "tag"):
418 w("&".join([("%s=%s" % (specialCases.get(h) or dashCapitalize(h), v)) for (h, v) in self.headers.items()]))
422 return self.toString()
425 return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host, self.port, self.transport)
428 def parseURL(url, host=None, port=None):
429 """Return string into URL object.
431 URIs are of of form 'sip:user@example.com'.
434 if not url.startswith("sip:"):
435 raise ValueError("unsupported scheme: " + url[:4])
436 parts = url[4:].split(";")
437 userdomain, params = parts[0], parts[1:]
438 udparts = userdomain.split("@", 1)
439 if len(udparts) == 2:
440 userpass, hostport = udparts
441 upparts = userpass.split(":", 1)
442 if len(upparts) == 1:
443 d["username"] = upparts[0]
445 d["username"] = upparts[0]
446 d["password"] = upparts[1]
448 hostport = udparts[0]
449 hpparts = hostport.split(":", 1)
450 if len(hpparts) == 1:
451 d["host"] = hpparts[0]
453 d["host"] = hpparts[0]
454 d["port"] = int(hpparts[1])
460 if p == params[-1] and "?" in p:
461 d["headers"] = h = {}
462 p, headers = p.split("?", 1)
463 for header in headers.split("&"):
464 k, v = header.split("=")
468 d.setdefault("other", []).append(p)
472 d["usertype"] = value
473 elif name in ("transport", "ttl", "maddr", "method", "tag"):
478 d.setdefault("other", []).append(p)
482 def cleanRequestURL(url):
483 """Clean a URL from a Request line."""
490 def parseAddress(address, host=None, port=None, clean=0):
491 """Return (name, uri, params) for From/To/Contact header.
493 @param clean: remove unnecessary info, usually for From and To headers.
495 address = address.strip()
496 # simple 'sip:foo' case
497 if address.startswith("sip:"):
498 return "", parseURL(address, host=host, port=port), {}
500 name, url = address.split("<", 1)
502 if name.startswith('"'):
504 if name.endswith('"'):
506 url, paramstring = url.split(">", 1)
507 url = parseURL(url, host=host, port=port)
508 paramstring = paramstring.strip()
510 for l in paramstring.split(";"):
521 return name, url, params
524 class SIPError(Exception):
525 def __init__(self, code, phrase=None):
527 phrase = statusCodes[code]
528 Exception.__init__(self, "SIP error (%d): %s" % (code, phrase))
533 class RegistrationError(SIPError):
534 """Registration was not possible."""
543 self.headers = util.OrderedDict() # map name to list of values
547 def addHeader(self, name, value):
549 name = longHeaders.get(name, name)
550 if name == "content-length":
551 self.length = int(value)
552 self.headers.setdefault(name,[]).append(value)
554 def bodyDataReceived(self, data):
557 def creationFinished(self):
558 if (self.length != None) and (self.length != len(self.body)):
559 raise ValueError, "wrong body length"
563 s = "%s\r\n" % self._getHeaderLine()
564 for n, vs in self.headers.items():
566 s += "%s: %s\r\n" % (specialCases.get(n) or dashCapitalize(n), v)
571 def _getHeaderLine(self):
572 raise NotImplementedError
575 class Request(Message):
576 """A Request for a URI"""
579 def __init__(self, method, uri, version="SIP/2.0"):
580 Message.__init__(self)
582 if isinstance(uri, URL):
585 self.uri = parseURL(uri)
586 cleanRequestURL(self.uri)
589 return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toString())
591 def _getHeaderLine(self):
592 return "%s %s SIP/2.0" % (self.method, self.uri.toString())
595 class Response(Message):
596 """A Response to a URI Request"""
598 def __init__(self, code, phrase=None, version="SIP/2.0"):
599 Message.__init__(self)
602 phrase = statusCodes[code]
606 return "<SIP Response %d:%s>" % (id(self), self.code)
608 def _getHeaderLine(self):
609 return "SIP/2.0 %s %s" % (self.code, self.phrase)
612 class MessagesParser(basic.LineReceiver):
613 """A SIP messages parser.
615 Expects dataReceived, dataDone repeatedly,
616 in that order. Shouldn't be connected to actual transport.
622 state = "firstline" # or "headers", "body" or "invalid"
626 def __init__(self, messageReceivedCallback):
627 self.messageReceived = messageReceivedCallback
630 def reset(self, remainingData=""):
631 self.state = "firstline"
632 self.length = None # body length
633 self.bodyReceived = 0 # how much of the body we received
635 self.setLineMode(remainingData)
637 def invalidMessage(self):
638 self.state = "invalid"
642 # clear out any buffered data that may be hanging around
643 self.clearLineBuffer()
644 if self.state == "firstline":
646 if self.state != "body":
649 if self.length == None:
650 # no content-length header, so end of data signals message done
652 elif self.length < self.bodyReceived:
653 # aborted in the middle
656 # we have enough data and message wasn't finished? something is wrong
657 raise RuntimeError, "this should never happen"
659 def dataReceived(self, data):
661 basic.LineReceiver.dataReceived(self, data)
664 self.invalidMessage()
666 def handleFirstLine(self, line):
667 """Expected to create self.message."""
668 raise NotImplementedError
670 def lineLengthExceeded(self, line):
671 self.invalidMessage()
673 def lineReceived(self, line):
674 if self.state == "firstline":
675 while line.startswith("\n") or line.startswith("\r"):
680 a, b, c = line.split(" ", 2)
682 self.invalidMessage()
684 if a == "SIP/2.0" and self.acceptResponses:
689 self.invalidMessage()
691 self.message = Response(code, c)
692 elif c == "SIP/2.0" and self.acceptRequests:
693 self.message = Request(a, b)
695 self.invalidMessage()
697 self.state = "headers"
700 assert self.state == "headers"
702 # XXX support multi-line headers
704 name, value = line.split(":", 1)
706 self.invalidMessage()
708 self.message.addHeader(name, value.lstrip())
709 if name.lower() == "content-length":
711 self.length = int(value.lstrip())
713 self.invalidMessage()
716 # CRLF, we now have message body until self.length bytes,
717 # or if no length was given, until there is no more data
718 # from the connection sending us data.
725 def messageDone(self, remainingData=""):
726 assert self.state == "body"
727 self.message.creationFinished()
728 self.messageReceived(self.message)
729 self.reset(remainingData)
731 def rawDataReceived(self, data):
732 assert self.state in ("body", "invalid")
733 if self.state == "invalid":
735 if self.length == None:
736 self.message.bodyDataReceived(data)
739 expectedLen = self.length - self.bodyReceived
740 if dataLen > expectedLen:
741 self.message.bodyDataReceived(data[:expectedLen])
742 self.messageDone(data[expectedLen:])
745 self.bodyReceived += dataLen
746 self.message.bodyDataReceived(data)
747 if self.bodyReceived == self.length:
751 class Base(protocol.DatagramProtocol):
752 """Base class for SIP clients and servers."""
759 self.parser = MessagesParser(self.addMessage)
761 def addMessage(self, msg):
762 self.messages.append(msg)
764 def datagramReceived(self, data, addr):
765 self.parser.dataReceived(data)
766 self.parser.dataDone()
767 for m in self.messages:
768 self._fixupNAT(m, addr)
770 log.msg("Received %r from %r" % (m.toString(), addr))
771 if isinstance(m, Request):
772 self.handle_request(m, addr)
774 self.handle_response(m, addr)
775 self.messages[:] = []
777 def _fixupNAT(self, message, (srcHost, srcPort)):
779 senderVia = parseViaHeader(message.headers["via"][0])
780 if senderVia.host != srcHost:
781 senderVia.received = srcHost
782 if senderVia.port != srcPort:
783 senderVia.rport = srcPort
784 message.headers["via"][0] = senderVia.toString()
785 elif senderVia.rport == True:
786 senderVia.received = srcHost
787 senderVia.rport = srcPort
788 message.headers["via"][0] = senderVia.toString()
790 def deliverResponse(self, responseMessage):
793 Destination is based on topmost Via header."""
794 destVia = parseViaHeader(responseMessage.headers["via"][0])
795 # XXX we don't do multicast yet
796 host = destVia.received or destVia.host
797 port = destVia.rport or destVia.port or self.PORT
798 destAddr = URL(host=host, port=port)
799 self.sendMessage(destAddr, responseMessage)
801 def responseFromRequest(self, code, request):
802 """Create a response to a request message."""
803 response = Response(code)
804 for name in ("via", "to", "from", "call-id", "cseq"):
805 response.headers[name] = request.headers.get(name, [])[:]
809 def sendMessage(self, destURL, message):
812 @param destURL: C{URL}. This should be a *physical* URL, not a logical one.
813 @param message: The message to send.
815 if destURL.transport not in ("udp", None):
816 raise RuntimeError, "only UDP currently supported"
818 log.msg("Sending %r to %r" % (message.toString(), destURL))
819 self.transport.write(message.toString(), (destURL.host, destURL.port or self.PORT))
821 def handle_request(self, message, addr):
822 """Override to define behavior for requests received
824 @type message: C{Message}
827 raise NotImplementedError
829 def handle_response(self, message, addr):
830 """Override to define behavior for responses received.
832 @type message: C{Message}
835 raise NotImplementedError
838 class IContact(Interface):
839 """A user of a registrar or proxy"""
843 def __init__(self, secondsToExpiry, contactURL):
844 self.secondsToExpiry = secondsToExpiry
845 self.contactURL = contactURL
847 class IRegistry(Interface):
848 """Allows registration of logical->physical URL mapping."""
850 def registerAddress(domainURL, logicalURL, physicalURL):
851 """Register the physical address of a logical URL.
853 @return: Deferred of C{Registration} or failure with RegistrationError.
856 def unregisterAddress(domainURL, logicalURL, physicalURL):
857 """Unregister the physical address of a logical URL.
859 @return: Deferred of C{Registration} or failure with RegistrationError.
862 def getRegistrationInfo(logicalURL):
863 """Get registration info for logical URL.
865 @return: Deferred of C{Registration} object or failure of LookupError.
869 class ILocator(Interface):
870 """Allow looking up physical address for logical URL."""
872 def getAddress(logicalURL):
873 """Return physical URL of server for logical URL of user.
875 @param logicalURL: a logical C{URL}.
876 @return: Deferred which becomes URL or fails with LookupError.
885 locator = None # object implementing ILocator
887 def __init__(self, host=None, port=PORT):
888 """Create new instance.
890 @param host: our hostname/IP as set in Via headers.
891 @param port: our port as set in Via headers.
893 self.host = host or socket.getfqdn()
898 """Return value of Via header for this proxy."""
899 return Via(host=self.host, port=self.port)
901 def handle_request(self, message, addr):
902 # send immediate 100/trying message before processing
903 #self.deliverResponse(self.responseFromRequest(100, message))
904 f = getattr(self, "handle_%s_request" % message.method, None)
906 f = self.handle_request_default
910 self.deliverResponse(self.responseFromRequest(e.code, message))
913 self.deliverResponse(self.responseFromRequest(500, message))
916 d.addErrback(lambda e:
917 self.deliverResponse(self.responseFromRequest(e.code, message))
920 def handle_request_default(self, message, (srcHost, srcPort)):
921 """Default request handler.
923 Default behaviour for OPTIONS and unknown methods for proxies
924 is to forward message on to the client.
926 Since at the moment we are stateless proxy, thats basically
929 def _mungContactHeader(uri, message):
930 message.headers['contact'][0] = uri.toString()
931 return self.sendMessage(uri, message)
933 viaHeader = self.getVia()
934 if viaHeader.toString() in message.headers["via"]:
935 # must be a loop, so drop message
936 log.msg("Dropping looped message.")
939 message.headers["via"].insert(0, viaHeader.toString())
940 name, uri, tags = parseAddress(message.headers["to"][0], clean=1)
942 # this is broken and needs refactoring to use cred
943 d = self.locator.getAddress(uri)
944 d.addCallback(self.sendMessage, message)
945 d.addErrback(self._cantForwardRequest, message)
947 def _cantForwardRequest(self, error, message):
948 error.trap(LookupError)
949 del message.headers["via"][0] # this'll be us
950 self.deliverResponse(self.responseFromRequest(404, message))
952 def deliverResponse(self, responseMessage):
955 Destination is based on topmost Via header."""
956 destVia = parseViaHeader(responseMessage.headers["via"][0])
957 # XXX we don't do multicast yet
958 host = destVia.received or destVia.host
959 port = destVia.rport or destVia.port or self.PORT
961 destAddr = URL(host=host, port=port)
962 self.sendMessage(destAddr, responseMessage)
964 def responseFromRequest(self, code, request):
965 """Create a response to a request message."""
966 response = Response(code)
967 for name in ("via", "to", "from", "call-id", "cseq"):
968 response.headers[name] = request.headers.get(name, [])[:]
971 def handle_response(self, message, addr):
972 """Default response handler."""
973 v = parseViaHeader(message.headers["via"][0])
974 if (v.host, v.port) != (self.host, self.port):
975 # we got a message not intended for us?
976 # XXX note this check breaks if we have multiple external IPs
977 # yay for suck protocols
978 log.msg("Dropping incorrectly addressed message")
980 del message.headers["via"][0]
981 if not message.headers["via"]:
982 # this message is addressed to us
983 self.gotResponse(message, addr)
985 self.deliverResponse(message)
987 def gotResponse(self, message, addr):
988 """Called with responses that are addressed at this server."""
991 class IAuthorizer(Interface):
992 def getChallenge(peer):
993 """Generate a challenge the client may respond to.
996 @param peer: The client's address
999 @return: The challenge string
1002 def decode(response):
1003 """Create a credentials object from the given response.
1005 @type response: C{str}
1008 class BasicAuthorizer:
1009 """Authorizer for insecure Basic (base64-encoded plaintext) authentication.
1011 This form of authentication is broken and insecure. Do not use it.
1014 implements(IAuthorizer)
1018 This method exists solely to issue a deprecation warning.
1021 "twisted.protocols.sip.BasicAuthorizer was deprecated "
1023 category=DeprecationWarning,
1027 def getChallenge(self, peer):
1030 def decode(self, response):
1031 # At least one SIP client improperly pads its Base64 encoded messages
1034 creds = (response + ('=' * i)).decode('base64')
1042 p = creds.split(':', 1)
1044 return UsernamePassword(*p)
1049 class DigestedCredentials(UsernameHashedPassword):
1050 """Yet Another Simple Digest-MD5 authentication scheme"""
1052 def __init__(self, username, fields, challenges):
1054 "twisted.protocols.sip.DigestedCredentials was deprecated "
1056 category=DeprecationWarning,
1058 self.username = username
1059 self.fields = fields
1060 self.challenges = challenges
1062 def checkPassword(self, password):
1064 response = self.fields.get('response')
1065 uri = self.fields.get('uri')
1066 nonce = self.fields.get('nonce')
1067 cnonce = self.fields.get('cnonce')
1068 nc = self.fields.get('nc')
1069 algo = self.fields.get('algorithm', 'MD5')
1070 qop = self.fields.get('qop-options', 'auth')
1071 opaque = self.fields.get('opaque')
1073 if opaque not in self.challenges:
1075 del self.challenges[opaque]
1077 user, domain = self.username.split('@', 1)
1079 uri = 'sip:' + domain
1081 expected = DigestCalcResponse(
1082 DigestCalcHA1(algo, user, domain, password, nonce, cnonce),
1083 nonce, nc, cnonce, qop, method, uri, None,
1086 return expected == response
1088 class DigestAuthorizer:
1089 CHALLENGE_LIFETIME = 15
1091 implements(IAuthorizer)
1095 "twisted.protocols.sip.DigestAuthorizer was deprecated "
1097 category=DeprecationWarning,
1100 self.outstanding = {}
1104 def generateNonce(self):
1105 c = tuple([random.randrange(sys.maxint) for _ in range(3)])
1109 def generateOpaque(self):
1110 return str(random.randrange(sys.maxint))
1112 def getChallenge(self, peer):
1113 c = self.generateNonce()
1114 o = self.generateOpaque()
1115 self.outstanding[o] = c
1119 'qop-options="auth"',
1123 def decode(self, response):
1124 response = ' '.join(response.splitlines())
1125 parts = response.split(',')
1126 auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1) for p in parts]])
1128 username = auth['username']
1132 return DigestedCredentials(username, auth, self.outstanding)
1137 class RegisterProxy(Proxy):
1138 """A proxy that allows registration for a specific domain.
1140 Unregistered users won't be handled.
1145 registry = None # should implement IRegistry
1149 def __init__(self, *args, **kw):
1150 Proxy.__init__(self, *args, **kw)
1151 self.liveChallenges = {}
1152 if "digest" not in self.authorizers:
1153 self.authorizers["digest"] = DigestAuthorizer()
1155 def handle_ACK_request(self, message, (host, port)):
1157 # ACKs are a client's way of indicating they got the last message
1158 # Responding to them is not a good idea.
1159 # However, we should keep track of terminal messages and re-transmit
1160 # if no ACK is received.
1163 def handle_REGISTER_request(self, message, (host, port)):
1164 """Handle a registration request.
1166 Currently registration is not proxied.
1168 if self.portal is None:
1169 # There is no portal. Let anyone in.
1170 self.register(message, host, port)
1172 # There is a portal. Check for credentials.
1173 if not message.headers.has_key("authorization"):
1174 return self.unauthorized(message, host, port)
1176 return self.login(message, host, port)
1178 def unauthorized(self, message, host, port):
1179 m = self.responseFromRequest(401, message)
1180 for (scheme, auth) in self.authorizers.iteritems():
1181 chal = auth.getChallenge((host, port))
1183 value = '%s realm="%s"' % (scheme.title(), self.host)
1185 value = '%s %s,realm="%s"' % (scheme.title(), chal, self.host)
1186 m.headers.setdefault('www-authenticate', []).append(value)
1187 self.deliverResponse(m)
1190 def login(self, message, host, port):
1191 parts = message.headers['authorization'][0].split(None, 1)
1192 a = self.authorizers.get(parts[0].lower())
1195 c = a.decode(parts[1])
1200 self.deliverResponse(self.responseFromRequest(500, message))
1202 c.username += '@' + self.host
1203 self.portal.login(c, None, IContact
1204 ).addCallback(self._cbLogin, message, host, port
1205 ).addErrback(self._ebLogin, message, host, port
1206 ).addErrback(log.err
1209 self.deliverResponse(self.responseFromRequest(501, message))
1211 def _cbLogin(self, (i, a, l), message, host, port):
1212 # It's stateless, matey. What a joke.
1213 self.register(message, host, port)
1215 def _ebLogin(self, failure, message, host, port):
1216 failure.trap(cred.error.UnauthorizedLogin)
1217 self.unauthorized(message, host, port)
1219 def register(self, message, host, port):
1220 """Allow all users to register"""
1221 name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
1223 if message.headers.has_key("contact"):
1224 contact = message.headers["contact"][0]
1226 if message.headers.get("expires", [None])[0] == "0":
1227 self.unregister(message, toURL, contact)
1229 # XXX Check expires on appropriate URL, and pass it to registry
1230 # instead of having registry hardcode it.
1231 if contact is not None:
1232 name, contactURL, params = parseAddress(contact, host=host, port=port)
1233 d = self.registry.registerAddress(message.uri, toURL, contactURL)
1235 d = self.registry.getRegistrationInfo(toURL)
1236 d.addCallbacks(self._cbRegister, self._ebRegister,
1237 callbackArgs=(message,),
1238 errbackArgs=(message,)
1241 def _cbRegister(self, registration, message):
1242 response = self.responseFromRequest(200, message)
1243 if registration.contactURL != None:
1244 response.addHeader("contact", registration.contactURL.toString())
1245 response.addHeader("expires", "%d" % registration.secondsToExpiry)
1246 response.addHeader("content-length", "0")
1247 self.deliverResponse(response)
1249 def _ebRegister(self, error, message):
1250 error.trap(RegistrationError, LookupError)
1251 # XXX return error message, and alter tests to deal with
1252 # this, currently tests assume no message sent on failure
1254 def unregister(self, message, toURL, contact):
1256 expires = int(message.headers["expires"][0])
1258 self.deliverResponse(self.responseFromRequest(400, message))
1264 name, contactURL, params = parseAddress(contact)
1265 d = self.registry.unregisterAddress(message.uri, toURL, contactURL)
1266 d.addCallback(self._cbUnregister, message
1267 ).addErrback(self._ebUnregister, message
1270 def _cbUnregister(self, registration, message):
1271 msg = self.responseFromRequest(200, message)
1272 msg.headers.setdefault('contact', []).append(registration.contactURL.toString())
1273 msg.addHeader("expires", "0")
1274 self.deliverResponse(msg)
1276 def _ebUnregister(self, registration, message):
1280 class InMemoryRegistry:
1281 """A simplistic registry for a specific domain."""
1283 implements(IRegistry, ILocator)
1285 def __init__(self, domain):
1286 self.domain = domain # the domain we handle registration for
1287 self.users = {} # map username to (IDelayedCall for expiry, address URI)
1289 def getAddress(self, userURI):
1290 if userURI.host != self.domain:
1291 return defer.fail(LookupError("unknown domain"))
1292 if self.users.has_key(userURI.username):
1293 dc, url = self.users[userURI.username]
1294 return defer.succeed(url)
1296 return defer.fail(LookupError("no such user"))
1298 def getRegistrationInfo(self, userURI):
1299 if userURI.host != self.domain:
1300 return defer.fail(LookupError("unknown domain"))
1301 if self.users.has_key(userURI.username):
1302 dc, url = self.users[userURI.username]
1303 return defer.succeed(Registration(int(dc.getTime() - time.time()), url))
1305 return defer.fail(LookupError("no such user"))
1307 def _expireRegistration(self, username):
1309 dc, url = self.users[username]
1311 return defer.fail(LookupError("no such user"))
1314 del self.users[username]
1315 return defer.succeed(Registration(0, url))
1317 def registerAddress(self, domainURL, logicalURL, physicalURL):
1318 if domainURL.host != self.domain:
1319 log.msg("Registration for domain we don't handle.")
1320 return defer.fail(RegistrationError(404))
1321 if logicalURL.host != self.domain:
1322 log.msg("Registration for domain we don't handle.")
1323 return defer.fail(RegistrationError(404))
1324 if self.users.has_key(logicalURL.username):
1325 dc, old = self.users[logicalURL.username]
1328 dc = reactor.callLater(3600, self._expireRegistration, logicalURL.username)
1329 log.msg("Registered %s at %s" % (logicalURL.toString(), physicalURL.toString()))
1330 self.users[logicalURL.username] = (dc, physicalURL)
1331 return defer.succeed(Registration(int(dc.getTime() - time.time()), physicalURL))
1333 def unregisterAddress(self, domainURL, logicalURL, physicalURL):
1334 return self._expireRegistration(logicalURL.username)