1
# -*- test-case-name: twisted.test.test_sip -*-
3
# Copyright (c) 2001-2009 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)