1
# Copyright 2005 Divmod, Inc. See LICENSE file for details
2
# -*- test-case-name: mantissa.test.test_sip -*-
4
# "I have always wished that my computer would be as easy to use as my
5
# telephone. My wish has come true. I no longer know how to use my
6
# telephone." - Bjarne Stroustrup
8
import socket, random, md5, sys, urllib
10
from twisted.python import log, util
11
from twisted.internet import protocol, defer, reactor, abstract
12
from twisted.names import client
14
from twisted.cred import credentials
15
from twisted.cred.error import UnauthorizedLogin
16
from twisted.protocols import basic
18
from zope.interface import Interface, implements
20
from axiom.userbase import Preauthenticated
21
from axiom.errors import NoSuchUser
23
from epsilon.modal import mode, ModalType
31
VIA_COOKIE = "z9hG4bK"
33
DEFAULT_REGISTRATION_LIFETIME = 3600
35
# SIP headers have short forms
36
shortHeaders = {"call-id": "i",
38
"content-encoding": "e",
39
"content-length": "l",
48
for k, v in shortHeaders.items():
55
181: "Call Is Being Forwarded",
60
300: "Multiple Choices",
61
301: "Moved Permanently",
62
302: "Moved Temporarily",
65
380: "Alternative Service",
69
402: "Payment Required",
72
406: "Not Acceptable",
73
407: "Proxy Authentication Required",
74
408: "Request Timeout",
77
411: "Length Required",
78
413: "Request Entity Too Large",
79
414: "Request-URI Too Large",
80
415: "Unsupported Media Type",
82
480: "Temporarily not available",
83
481: "Call Leg/Transaction Does Not Exist",
86
484: "Address Incomplete",
89
487: "Request Cancelled",
91
500: "Internal Server Error",
92
501: "Not Implemented",
94
503: "Service Unavailable",
95
504: "Gateway Time-out",
96
505: "SIP Version not supported",
98
600: "Busy Everywhere",
100
604: "Does not exist anywhere",
101
606: "Not Acceptable",
103
# some headers change case strangely.
106
'call-id': 'Call-ID',
107
'www-authenticate': 'WWW-Authenticate',
108
'proxy-authenticate': 'Proxy-Authenticate',
109
'proxy-authorization':'Proxy-Authorization',
110
'record-route':'Record-Route',
124
m.update(pszUserName)
128
m.update(pszPassword)
130
if pszAlg == "md5-sess":
138
return HA1.encode('hex')
140
def DigestCalcResponse(
153
m.update(pszDigestUri)
154
if pszQop == "auth-int":
157
HA2 = m.digest().encode('hex')
164
if pszNonceCount and pszCNonce: # pszQop:
165
m.update(pszNonceCount)
172
hash = m.digest().encode('hex')
176
"""A SIP Via header."""
178
def __init__(self, host, port=PORT, transport="UDP", ttl=None, hidden=False,
179
received=None, rport=None, branch=None, maddr=None):
180
self.transport = transport
185
self.received = received
191
s = "SIP/2.0/%s %s:%s" % (self.transport, self.host, self.port)
194
for n in "ttl", "branch", "maddr", "received", "rport":
195
value = getattr(self, n)
199
s += ";%s=%s" % (n, value)
203
def parseViaHeader(value):
204
"""Parse a Via header, returning Via class instance."""
206
parts = value.split(";")
207
sent, params = parts[0], parts[1:]
208
protocolinfo, by = sent.split(" ", 1)
210
pname, pversion, transport = protocolinfo.split("/")
211
if pname != "SIP" or pversion != "2.0":
212
raise SIPError(400, "wrong protocol or version: %r" % value)
213
result["transport"] = transport
215
host, port = by.split(":")
216
result["port"] = int(port)
217
result["host"] = host
221
# it's the comment-striping dance!
222
p = p.strip().split(" ", 1)
224
p, comment = p[0], ""
228
result["hidden"] = True
230
parts = p.split("=", 1)
232
name, value = parts[0], True
235
if name in ("rport", "ttl"):
245
def __init__(self, host, username=None, password=None, port=None,
246
transport=None, usertype=None, method=None,
247
ttl=None, maddr=None, tag=None, other=None, headers=None):
248
self.username = username
250
self.password = password
252
self.transport = transport
253
self.usertype = usertype
265
self.headers = headers
267
def toCredString(self):
268
return '%s@%s' % (self.username, self.host)
273
if self.username != None:
274
w(urllib.quote(self.username))
275
if self.password != None:
276
w(":%s" % (urllib.quote(self.password)))
279
if self.port != None:
281
if self.usertype != None:
282
w(";user=%s" % self.usertype)
283
for n in ("transport", "ttl", "maddr", "method", "tag"):
286
w(";%s=%s" % (urllib.quote(n), urllib.quote(v)))
287
for k, v in self.other.iteritems():
289
w(";%s=%s" % (urllib.quote(k), urllib.quote(v)))
294
w("&".join([("%s=%s" % (specialCases.get(h) or urllib.quote(h).capitalize(), urllib.quote(v))) for (h, v) in self.headers.items()]))
298
return self.toString()
301
return '<sip.URL %s>' % self.toString()
303
def __cmp__(self, other):
304
return cmp(self.__dict__, other.__dict__)
307
#I could include the other stuff but what's the point?
308
#this is the most usual stuff and python is very kind to collisions
309
return hash((self.host, self.username, self.port, tuple(self.headers.items())))
311
def parseURL(url, host=None, port=None):
312
"""Return string into URL object.
314
URIs are of of form 'sip:user@example.com'.
317
if not url.startswith("sip:"):
318
raise SIPError(416, "Unsupported URI scheme: " + url[:4])
319
parts = url[4:].split(";")
320
userdomain, params = parts[0], parts[1:]
321
udparts = userdomain.split("@", 1)
322
if len(udparts) == 2:
323
userpass, hostport = udparts
324
upparts = userpass.split(":", 1)
325
if len(upparts) == 1:
326
d["username"] = urllib.unquote(upparts[0])
328
d["username"] = urllib.unquote(upparts[0])
329
d["password"] = urllib.unquote(upparts[1])
331
hostport = udparts[0]
332
hpparts = hostport.split(":", 1)
333
if len(hpparts) == 1:
334
d["host"] = hpparts[0]
336
d["host"] = hpparts[0]
337
d["port"] = int(hpparts[1])
343
if p == params[-1] and "?" in p:
344
d["headers"] = h = {}
345
p, headers = p.split("?", 1)
346
for header in headers.split("&"):
347
k, v = header.split("=")
348
h[urllib.unquote(k)] = urllib.unquote(v)
351
d.setdefault("other", {})[urllib.unquote(p)] = ''
353
name, value = map(urllib.unquote, nv)
355
d["usertype"] = value
356
elif name in ("transport", "ttl", "maddr", "method", "tag"):
361
d.setdefault("other", {})[name] = value
365
def cleanRequestURL(url):
366
"""Clean a URL from a Request line."""
373
def parseAddress(address, host=None, port=None, clean=0):
374
"""Return (name, uri, params) for From/To/Contact header.
376
@param clean: remove unnecessary info, usually for From and To headers.
378
Although many headers such as From can contain any valid URI, even those
379
with schemes other than 'sip', this function raises SIPError if the scheme
380
is not 'sip' because the upper layers do not support it.
382
def splitParams(paramstring):
384
paramstring = paramstring.strip()
386
for l in paramstring.split(";"):
396
address = address.strip()
397
# simple 'sip:foo' case
398
if not '<' in address:
399
i = address.rfind(";tag=")
402
params = splitParams(address[i:])
403
address = address[:i]
406
return "", parseURL(address, host=host, port=port), params
408
name, url = address.split("<", 1)
410
if name.startswith('"'):
412
if name.endswith('"'):
415
name = re.sub(r'\\(.)', r'\1', name)
416
url, paramstring = url.split(">", 1)
417
url = parseURL(url, host=host, port=port)
418
params = splitParams(paramstring)
425
return name.decode('utf8','replace'), url, params
435
def __init__(self, version):
436
self.headers = util.OrderedDict() # map name to list of values
439
self.version = version
442
c = Message(self.version)
443
c.headers = self.headers.copy()
445
c.finished = self.finished
448
def __eq__(self, other):
449
return (other.__class__ == self.__class__
450
and self.version == other.version
451
and dict([(k,v) for k,v in self.headers.items() if v]) == dict([(k,v) for k,v in other.headers.items() if v])
452
and self.body == other.body)
454
def addHeader(self, name, value):
456
name = longHeaders.get(name, name)
457
if name == "content-length":
458
self.length = int(value)
459
self.headers.setdefault(name,[]).append(value)
461
def bodyDataReceived(self, data):
464
def creationFinished(self):
465
if (self.length != None) and (self.length != len(self.body)):
466
raise ValueError, "wrong body length"
470
s = "%s\r\n" % self._getHeaderLine()
471
for n, vs in self.headers.items():
473
s += "%s: %s\r\n" % (specialCases.get(n) or n.capitalize(), v)
478
def _getHeaderLine(self):
479
raise NotImplementedError
482
class Request(Message):
483
"""A Request for a URI"""
486
def __init__(self, method, uri, version="SIP/2.0"):
487
Message.__init__(self, version)
489
if isinstance(uri, URL):
492
self.uri = parseURL(uri)
493
cleanRequestURL(self.uri)
496
c = Message.copy(self)
497
c.__class__ = Request
498
c.method = self.method
502
def __eq__(self, other):
503
return Message.__eq__(self, other) and self.method == other.method and self.uri == other.uri
506
return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toString())
508
def _getHeaderLine(self):
509
return "%s %s %s" % (self.method, self.uri.toString(), self.version)
512
class Response(Message):
513
"""A Response to a URI Request"""
515
def __init__(self, code, phrase=None, version="SIP/2.0"):
516
Message.__init__(self, version)
519
phrase = statusCodes[code]
522
def __eq__(self, other):
523
return Message.__eq__(self, other) and self.code == other.code
526
return "<SIP Response %d:%s>" % (id(self), self.code)
528
def _getHeaderLine(self):
529
return "SIP/2.0 %s %s" % (self.code, self.phrase)
531
def splitMultiHeader(s):
532
"Split a header on commas, ignoring commas in quotes and escaped quotes."
536
for i in xrange(len(s)):
544
if not quoted and s[i] == ',':
545
headers.append(s[last:i])
547
headers.append(s[last:])
552
class MessagesParser(basic.LineReceiver):
553
"""A SIP messages parser.
555
Expects dataReceived, dataDone repeatedly,
556
in that order. Shouldn't be connected to actual transport.
562
state = "firstline" # or "headers", "body" or "invalid"
563
multiheaders = ['accept','accept-encoding', 'accept-language', 'alert-info', 'allow', 'authentication-info', 'call-info', 'content-encoding', 'content-language', 'error-info', 'in-reply-to', 'proxy-require', 'require', 'supported', 'unsupported', 'via', 'warning']
564
multiAddressHeaders = ['route', 'record-route', 'contact']
567
def __init__(self, messageReceivedCallback):
568
self.messageReceived = messageReceivedCallback
571
def reset(self, remainingData=""):
572
self.state = "firstline"
573
self.length = None # body length
574
self.bodyReceived = 0 # how much of the body we received
576
self.setLineMode(remainingData)
578
def invalidMessage(self, exc=None):
580
if isinstance(exc, SIPError):
586
# clear out any buffered data that may be hanging around
587
self.clearLineBuffer()
588
if self.state == "firstline":
590
if self.state != "body":
593
if self.length == None:
594
# no content-length header, so end of data signals message done
596
elif self.length < self.bodyReceived:
597
# aborted in the middle
600
# we have enough data and message wasn't finished? something is wrong
601
raise RuntimeError, "corrupted or overflowed SIP packet"
603
def dataReceived(self, data):
605
basic.LineReceiver.dataReceived(self, data)
608
self.invalidMessage(e)
610
def handleFirstLine(self, line):
611
"""Expected to create self.message."""
612
raise NotImplementedError
614
def lineLengthExceeded(self, line):
615
self.invalidMessage()
617
def lineReceived(self, line):
618
if self.state == "firstline":
619
while line.startswith("\n") or line.startswith("\r"):
624
a, b, c = line.split(" ", 2)
626
self.invalidMessage()
628
if a == "SIP/2.0" and self.acceptResponses:
633
self.invalidMessage()
635
self.message = Response(code, c)
636
elif c == "SIP/2.0" and self.acceptRequests:
637
self.message = Request(a, b)
639
self.invalidMessage()
641
self.state = "headers"
645
assert self.state == "headers"
649
#leading whitespace: this is a continuation line.
655
self.processHeaderLine(self.prevline)
657
self.invalidMessage()
662
# CRLF, we now have message body until self.length bytes,
663
# or if no length was given, until there is no more data
664
# from the connection sending us data.
667
self.processHeaderLine(self.prevline)
669
self.invalidMessage()
676
def processHeaderLine(self, line):
677
name, value = line.split(":", 1)
678
name, value = name.rstrip().lower(), value.lstrip()
680
if name in self.multiheaders:
681
multi = value.split(',')
684
self.message.addHeader(name, v.strip())
686
self.message.addHeader(v)
687
elif name in self.multiAddressHeaders:
688
for val in splitMultiHeader(value):
689
self.message.addHeader(name, val)
691
self.message.addHeader(name, value)
692
if name.lower() == "content-length":
693
self.length = int(value.lstrip())
695
def messageDone(self, remainingData=""):
696
assert self.state == "body"
697
self.message.creationFinished()
698
self.messageReceived(self.message)
699
self.reset(remainingData)
701
def rawDataReceived(self, data):
702
if self.length == None:
703
self.message.bodyDataReceived(data)
706
expectedLen = self.length - self.bodyReceived
707
if dataLen > expectedLen:
708
self.message.bodyDataReceived(data[:expectedLen])
709
self.messageDone(data[expectedLen:])
712
self.bodyReceived += dataLen
713
self.message.bodyDataReceived(data)
714
if self.bodyReceived == self.length:
719
class SIPError(Exception):
720
def __init__(self, code, phrase=None):
722
phrase = statusCodes[code]
723
Exception.__init__(self, "SIP error (%d): %s" % (code, phrase))
727
class SIPLookupError(SIPError):
728
"""An error raised specifically for SIP lookup errors.
730
def __init__(self, code=404, phrase=None):
731
SIPError.__init__(self, code=code, phrase=phrase)
733
class RegistrationError(SIPError):
734
"""Registration was not possible."""
736
class ISIPEvent(Interface):
737
"A log message concerning SIP"
740
class IAuthorizer(Interface):
741
def getChallenge(peer):
742
"""Generate a challenge the client may respond to.
745
@param peer: The client's address
748
@return: The challenge string
751
def decode(response):
752
"""Create a credentials object from the given response.
754
@type response: C{str}
759
class BasicAuthorizer:
760
"""Authorizer for insecure Basic (base64-encoded plaintext) authentication.
762
This form of authentication is broken and insecure. Do not use it.
765
implements(IAuthorizer)
767
def getChallenge(self, peer):
770
def decode(self, response):
771
# At least one SIP client improperly pads its Base64 encoded messages
774
creds = (response + ('=' * i)).decode('base64')
782
p = creds.split(':', 1)
784
return credentials.UsernamePassword(*p)
787
class DigestedCredentials(credentials.UsernameHashedPassword):
788
"""Yet Another Simple Digest-MD5 authentication scheme"""
790
def __init__(self, username, fields, challenges):
791
self.username = username
793
self.challenges = challenges
795
def checkPassword(self, password):
797
response = self.fields.get('response')
798
uri = self.fields.get('uri')
799
nonce = self.fields.get('nonce')
800
cnonce = self.fields.get('cnonce')
801
nc = self.fields.get('nc')
802
algo = self.fields.get('algorithm', 'MD5')
803
qop = self.fields.get('qop', 'auth')
804
opaque = self.fields.get('opaque')
806
if opaque not in self.challenges:
808
del self.challenges[opaque]
810
user, domain = self.username.split('@', 1)
812
uri = 'sip:' + domain
814
expected = DigestCalcResponse(
815
DigestCalcHA1(algo, user, domain, password, nonce, cnonce),
816
nonce, nc, cnonce, qop, method, uri, None,
819
return expected == response
821
class DigestAuthorizer:
822
CHALLENGE_LIFETIME = 15
824
implements(IAuthorizer)
827
self.outstanding = {}
829
def generateNonce(self):
830
c = tuple([random.randrange(sys.maxint) for _ in range(3)])
834
def generateOpaque(self):
835
return str(random.randrange(sys.maxint))
837
def getChallenge(self, peer):
838
c = self.generateNonce()
839
o = self.generateOpaque()
840
self.outstanding[o] = c
848
def decode(self, response):
850
if s[0] == s[-1] == '"':
853
response = ' '.join(response.splitlines())
854
parts = response.split(',')
855
auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1) for p in parts]])
857
username = auth['username']
861
return DigestedCredentials(username, auth, self.outstanding)
865
def responseFromRequest(code, request):
866
response = Response(code)
867
for name in ("via", "to", "from", "call-id", "cseq"):
868
response.headers[name] = request.headers.get(name, [])[:]
872
def computeBranch(msg):
873
"""Create a branch tag to uniquely identify this message. See
874
RFC3261 sections 8.1.1.7 and 16.6.8."""
875
if msg.headers.has_key('via') and msg.headers['via']:
876
oldvia = msg.headers['via'][0]
879
return VIA_COOKIE + md5.new((parseAddress(msg.headers['to'][0])[2].get('tag','') +
880
parseAddress(msg.headers['from'][0])[2].get('tag','')+
881
msg.headers['call-id'][0] +
884
msg.headers['cseq'][0].split(' ')[0])
887
class IContact(Interface):
888
"""A user of a registrar or proxy"""
890
def registerAddress(physicalURL, expiry):
891
"""Register the physical address of a logical URL.
893
@return: Deferred of C{Registration} or failure with RegistrationError.
896
def unregisterAddress():
897
"""Unregister the physical address of a logical URL.
899
@return: Deferred of C{Registration} or failure with RegistrationError.
902
def getRegistrationInfo():
903
"""Get registration info for logical URL.
905
@return: Deferred of C{Registration} object or failure of SIPLookupError.
908
def callIncoming(name, callerURI, callerContact):
909
"""Record an incoming call with a user's name, the incoming
910
SIP URI, and, if they are registered with our system, their
911
caller IContact implementor.
913
You may *decline* an incoming call by raising an exception in
914
this method. A SIPError is preferred.
917
def callOutgoing(name, calleeURI):
918
"""Record an outgoing call.
926
class ClientInviteTransaction(object):
927
__metaclass__ = ModalType
928
initialMode = 'calling'
929
modeAttribute = 'mode'
931
def __init__(self, transport, tu, invite, peerURL):
932
self.transport = transport
934
self.request = invite
937
self.waitingToCancel = False
938
self.branch = computeBranch(invite)
939
self.transport.clientTransactions[self.branch] = self
942
def transitionTo(self, stateName):
944
self.mode = stateName
947
def sendInvite(self):
948
self.transport.sendRequest(self.request, self.peer)
950
def transportError(self, err):
951
self.tu.transportError(self, err)
952
self.transitionTo('terminated')
956
"Builds an ACK according to the rules in 17.1.1.3, RFC3261."
957
ack = Request('ACK',self.request.uri)
958
for name in ("from", "call-id", 'route'):
959
ack.headers[name] = self.request.headers.get(name, [])[:]
960
ack.addHeader('cseq', "%s ACK" % self.request.headers['cseq'][0].split(' ',1)[0])
961
ack.headers['to'] = msg.headers['to']
962
ack.headers['max-forwards'] = ['70']
963
ack.addHeader('via', Via(self.transport.host,
966
branch=self.branch).toString())
967
self.transport.sendRequest(ack, self.peer)
969
def sendCancel(self):
970
cancel = Request("CANCEL", self.request.uri)
971
for hdr in ('from','to','call-id'):
972
cancel.addHeader(hdr, self.request.headers[hdr][0])
973
cancel.addHeader('max-forwards','70')
974
cancel.addHeader('cseq', "%s CANCEL" % self.request.headers['cseq'][0].split(' ',1)[0])
975
cancel.addHeader('via', Via(self.transport.host,
978
branch=self.branch).toString())
979
self.transport.sendRequest(cancel, self.peer)
985
debug("ClientInvite %s transitioning to 'calling'" % (self.peer,))
991
self.timerA = reactor.callLater(self.timerATries*T1,
995
self.timerB = reactor.callLater(64*T1, self.timeout)
997
def messageReceived(self, msg):
999
self.tu.responseReceived(msg,self)
1000
if 100 <= msg.code < 200:
1001
if self.waitingToCancel:
1004
self.transitionTo('proceeding')
1005
elif 200 <= msg.code < 300:
1006
self.transitionTo('terminated')
1007
elif 300 <= msg.code < 700:
1009
self.transitionTo('completed')
1013
if self.timerA.active():
1014
self.timerA.cancel()
1015
if self.timerB.active():
1016
self.timerB.cancel()
1019
if self.waitingToCancel:
1020
self.response = responseFromRequest(487, self.request)
1022
self.response = responseFromRequest(408, self.request)
1023
self.transitionTo('terminated')
1026
self.waitingToCancel = True
1028
class proceeding(mode):
1030
debug("ClientInvite %s transitioning to 'proceeding'" % (self.peer,))
1032
def messageReceived(self, msg):
1034
self.tu.responseReceived(msg, self)
1035
if 100 <= msg.code < 200:
1037
elif 200 <= msg.code < 300:
1038
self.transitionTo('terminated')
1039
elif 300 <= msg.code < 700:
1041
self.transitionTo('completed')
1044
if self.timerB.active():
1045
self.timerB.cancel()
1049
#not exactly timer B but it oughta ba
1050
self.timerB = reactor.callLater(64*T1, self.cancel)
1055
if self.waitingToCancel:
1056
self.response = responseFromRequest(487, self.request)
1058
self.response = responseFromRequest(408, self.request)
1059
self.transitionTo('terminated')
1061
class completed(mode):
1064
debug("ClientInvite %s transitioning to 'completed'" % (self.peer,))
1065
self.timerD = reactor.callLater(32, self.transitionTo,
1068
def messageReceived(self, msg):
1073
if self.timerD.active():
1074
self.timerD.cancel()
1079
class terminated(mode):
1082
debug("ClientInvite %s transitioning to 'terminated'" % (self.peer,))
1084
for k, v in self.transport.clientTransactions.iteritems():
1086
del self.transport.clientTransactions[k]
1088
self.tu.clientTransactionTerminated(self)
1090
def messageReceived(self, msg):
1094
raise RuntimeError, "can't unterminate a transaction"
1101
class ClientTransaction(object):
1102
__metaclass__ = ModalType
1103
initialMode = 'trying'
1104
modeAttribute = 'mode'
1105
def __init__(self, transport, tu, request, peerURL):
1107
self.transport = transport
1108
self.request = request
1110
self.response = None
1111
branch = computeBranch(request)
1112
self.transport.clientTransactions[branch] = self
1115
def transitionTo(self, stateName):
1117
self.mode = stateName
1120
def transportError(self, err):
1121
self.tu.transportError(self, err)
1122
self.transitionTo('terminated')
1124
def sendRequest(self):
1125
self.transport.sendRequest(self.request, self.peer)
1130
debug("Client %s transitioning to 'trying'" % (self.peer,))
1131
self.timerETries = 0
1133
self.timerETries += 1
1135
self.timerE = reactor.callLater(min((2**self.timerETries)*T1, T2),
1138
self.timerF = reactor.callLater(64*T1, self.transitionTo, 'terminated')
1140
def messageReceived(self, msg):
1143
self.transitionTo('completed')
1145
self.transitionTo('proceeding')
1146
self.tu.responseReceived(msg, self)
1150
if self.timerE.active():
1151
self.timerE.cancel()
1152
if self.timerF.active():
1153
self.timerF.cancel()
1155
class proceeding(mode):
1158
debug("Client %s transitioning to 'proceeding'" % (self.peer,))
1159
self.timerETries = 0
1161
self.timerETries += 1
1163
reactor.callLater(T2, timerERetry)
1165
self.timerF = reactor.callLater(64*T1, self.transitionTo, 'terminated')
1167
def messageReceived(self, msg):
1169
self.transitionTo('completed')
1171
self.tu.responseReceived(msg, self)
1174
if self.timerE.active():
1175
self.timerE.cancel()
1176
if self.timerF.active():
1177
self.timerF.cancel()
1179
class completed(mode):
1182
debug("Client %s transitioning to 'completed'" % (self.peer,))
1183
self.timerK = reactor.callLater(T4, self.transitionTo,
1186
def messageReceived(self, msg):
1188
The "Completed" state exists to buffer any additional response
1189
retransmissions that may be received (which is why the client
1190
transaction remains there only for unreliable transports).
1193
if self.timerK.active():
1194
self.timerK.cancel()
1197
class terminated(mode):
1199
debug("Client %s transitioning to 'terminated'" % (self.peer,))
1201
for k, v in self.transport.clientTransactions.iteritems():
1203
del self.transport.clientTransactions[k]
1205
self.tu.clientTransactionTerminated(self)
1207
def messageReceived(self, msg):
1211
raise RuntimeError("can't unterminate a transaction")
1213
class ServerInviteTransaction(object):
1214
__metaclass__ = ModalType
1215
initialMode = 'proceeding'
1216
modeAttribute = 'mode'
1218
def __init__(self, transport, tu, message, peerURL):
1219
self.message = message
1221
self.transport = transport
1223
self.lastResponse = None
1225
def sentFinalResponse(self):
1226
return self.lastResponse.code >= 200
1228
def transitionTo(self, stateName):
1230
self.mode = stateName
1233
def send100(self, msg):
1234
self.sendResponse(responseFromRequest(100, msg))
1236
def respond(self, msg):
1237
self.lastResponse = msg
1238
self.transport.sendResponse(msg)
1240
def repeatLastResponse(self):
1241
self.transport.sendResponse(self.lastResponse)
1243
class proceeding(mode):
1246
debug("ServerInvite %s transitioning to 'proceeding'" % (self.peer,))
1251
def messageReceived(self, msg):
1252
if msg.method == "INVITE":
1253
self.repeatLastResponse()
1255
def messageReceivedFromTU(self, msg):
1257
if 200 <= msg.code < 300:
1258
self.transitionTo('terminated')
1259
elif 300 <= msg.code < 700:
1260
self.transitionTo('completed')
1263
class completed(mode):
1266
debug("ServerInvite %s transitioning to 'completed'" % (self.peer,))
1267
self.timerGTries = 1
1269
self.timerGTries +=1
1270
self.repeatLastResponse()
1271
self.timerG = reactor.callLater(min((2**self.timerGTries)*T1,
1273
self.timerG = reactor.callLater(T1, timerGRetry)
1274
self.timerH = reactor.callLater(64*T1,
1275
self.transitionTo, 'terminated')
1278
def messageReceived(self, msg):
1279
if msg.method == "INVITE":
1280
self.repeatLastResponse()
1281
elif msg.method == "ACK":
1282
self.transitionTo('confirmed')
1284
def messageReceivedFromTU(self, msg):
1288
if self.timerG.active():
1289
self.timerG.cancel()
1290
if self.timerH.active():
1291
self.timerH.cancel()
1294
class confirmed(mode):
1297
debug("ServerInvite %s transitioning to 'confirmed'" % (self.peer,))
1298
self.timerI = reactor.callLater(T4, self.transitionTo,
1301
def messageReceived(self, msg):
1304
def messageReceivedFromTU(self, msg):
1311
class terminated(mode):
1314
debug("ServerInvite %s transitioning to 'terminated'" % (self.peer,))
1315
self.transport.serverTransactionTerminated(self)
1317
def messageReceived(self, msg):
1320
def messageReceivedFromTU(self, msg):
1328
class ServerTransaction(object):
1329
__metaclass__ = ModalType
1330
initialMode = 'trying'
1331
modeAttribute = 'mode'
1333
def __init__(self, transport, tu, message, peerURL):
1334
self.message = message
1335
self.transport = transport
1338
self.lastResponse = None
1340
def transitionTo(self, stateName):
1342
self.mode = stateName
1345
def repeatLastResponse(self):
1346
self.transport.sendResponse(self.lastResponse)
1348
def respond(self, msg):
1349
self.lastResponse = msg
1350
self.transport.sendResponse(msg)
1355
debug("Server %s transitioning to 'trying'" % (self.peer,))
1357
def messageReceived(self, msg):
1360
def messageReceivedFromTU(self, msg):
1362
if 100 <= msg.code < 200:
1363
self.transitionTo('proceeding')
1365
self.transitionTo('completed')
1370
class proceeding(mode):
1373
debug("Server %s transitioning to 'proceeding'" % (self.peer,))
1375
def messageReceived(self, msg):
1376
self.repeatLastResponse()
1378
def messageReceivedFromTU(self, msg):
1380
if 200 <= msg.code < 700:
1381
self.transitionTo('completed')
1387
class completed(mode):
1390
debug("Server %s transitioning to 'completed'" % (self.peer,))
1391
self.timerJ = reactor.callLater(64*T1,
1392
self.transitionTo, 'terminated')
1394
def messageReceived(self, msg):
1395
self.repeatLastResponse()
1397
def messageReceivedFromTU(self, msg):
1401
if self.timerJ.active():
1402
self.timerJ.cancel()
1405
class terminated(mode):
1408
debug("Server %s transitioning to 'terminated'" % (self.peer,))
1409
self.transport.serverTransactionTerminated(self)
1411
def messageReceived(self, msg):
1414
def messageReceivedFromTU(self, msg):
1423
###############################################################################
1425
class SIPTransport(protocol.DatagramProtocol):
1428
debug = debuggingEnabled
1430
def __init__(self, tu, hosts, port):
1431
"""tu: an implementor of ITransactionUser.
1432
hosts: A sequence of hostnames this element is
1433
authoritative for. The first is used as the name for
1434
outgoing messages. If empty, socket.getfqdn() is
1436
port: The port this element listens on."""
1439
self.parser = MessagesParser(self.addMessage)
1441
self.hosts = hosts or [socket.getfqdn()]
1442
self.host = self.hosts[0]
1444
self.serverTransactions = {}
1445
self.clientTransactions = {}
1448
def addMessage(self, msg):
1449
self.messages.append(msg)
1451
def datagramReceived(self, data, addr):
1453
self.parser.dataReceived(data)
1454
self.parser.dataDone()
1456
for m in self.messages:
1458
if isinstance(m, Request):
1462
debug("Received %r from %r." % (id, addr))
1463
if isinstance(m, Request):
1464
self._fixupNAT(m, addr)
1465
self.handle_request(m, addr)
1467
self.handle_response(m, addr)
1469
del self.messages[:]
1470
except Exception, e:
1472
if debuggingEnabled:
1475
self._badRequest(addr, e)
1477
def _badRequest(self, addr, e):
1478
#request parsing failed, we're going to have to make stuff up
1480
if isinstance(e, SIPError):
1486
r.addHeader("to", "%s:%s" % (addr))
1487
# see RFC3261 8.1.1.7, 16.6.8
1488
r.addHeader("via", Via(host=self.host, port=self.port, branch=VIA_COOKIE+ md5.new(repr(addr)).hexdigest()).toString())
1489
self.transport.write(r, addr)
1491
def _fixupNAT(self, message, (srcHost, srcPort)):
1493
senderVia = parseViaHeader(message.headers["via"][0])
1494
senderVia.received = srcHost
1495
if senderVia.rport == True:
1496
senderVia.rport = srcPort
1497
message.headers["via"][0] = senderVia.toString()
1499
def handle_request(self, msg, addr):
1501
via = parseViaHeader(msg.headers['via'][0])
1503
if not (via.branch and via.branch.startswith(VIA_COOKIE)):
1504
via.branch = computeBranch(msg)
1508
st = self.serverTransactions.get((via.branch, via.host,
1511
st.messageReceived(msg)
1513
def addNewServerTransaction(st):
1515
self.serverTransactions[(via.branch, via.host,
1516
via.port, msg.method)] = st
1517
return defer.maybeDeferred(self.tu.requestReceived, msg, addr
1518
).addCallback(addNewServerTransaction)
1521
def serverTransactionTerminated(self, st):
1523
for k,v in self.serverTransactions.iteritems():
1525
del self.serverTransactions[k]
1529
def handle_response(self, msg, addr):
1531
via = parseViaHeader(msg.headers['via'][0])
1532
if not (via.host in self.hosts and via.port == self.port):
1537
ct = self.clientTransactions.get(via.branch)
1538
if ct and msg.headers['cseq'][0].split(' ')[1] == ct.request.headers['cseq'][0].split(' ')[1]:
1539
ct.messageReceived(msg)
1541
self.tu.responseReceived(msg)
1544
def sendRequest(self, msg, target):
1545
"""Add a Via header to this message and send it to the (host,
1548
#CANCEL & ACK requires the same Via branch as the thing it is
1549
#cancelling/acking so it has to be added by the txn
1550
if msg.method not in ("ACK", "CANCEL"):
1551
#raaaaa this is so we don't add the same via header on resends
1553
if msg.headers.get('via'):
1554
msg.headers['via'] = msg.headers['via'][:]
1557
msg.headers.setdefault('via', []).insert(0, Via(self.host, self.port,
1559
branch=computeBranch(msg)).toString())
1560
txt = msg.toString()
1562
raise NotImplementedError, "Message too big for UDP. You're boned."
1563
debug("Sending %r to %r" % (msg.method, target))
1564
self._resolveA(target[0]).addCallback(
1565
lambda ip: self.sendMessage(msg, (ip, (target[1] or self.PORT))))
1568
def sendResponse(self, msg):
1569
"""Determine the target for the response and send it."""
1573
via = parseViaHeader(msg.headers['via'][0])
1574
host = via.received or via.host
1575
port = via.rport or via.port or self.PORT
1577
debug("Sending %r to %r" % (msg.code, (host, port)))
1578
self._resolveA(host).addCallback(
1579
lambda ip: self.sendMessage(msg, (ip, port)))
1581
def sendMessage(self, msg, (host, port)):
1583
self.transport.write(msg.toString(), (host, port))
1585
def _resolveA(self, addr):
1586
return reactor.resolve(addr)
1589
class ITransactionUser(Interface):
1590
def start(transport):
1591
"""Connects the transport to the TU."""
1593
def requestReceived(msg, addr):
1594
"""Processes a message, after the transport and transaction
1595
layer are finished with it. May return a ServerTransaction (or
1596
ServerInviteTransaction), which will handle subsequent
1597
messages from that SIP transaction."""
1599
def responseReceived(msg, ct=None):
1600
"""Processes a response received from the transport, along
1601
with the client transaction it is a part of, if any."""
1604
def clientTransactionTerminated(ct):
1605
"""Called when a client transaction created by this TU
1606
transitions to the 'terminated' state."""
1608
#from resiprocate's proxy
1609
responsePriorities = { 428: 24, 429: 24, 494: 24,
1610
412: 1, 413: 25, 414: 25,
1612
422: 3, 423: 3, 486: 30,
1613
407: 4, 401: 4, 480: 31,
1615
493: 10, 436: 33, 437: 33,
1617
406: 13, 415: 13, 488: 13, 404: 35,
1618
416: 20, 417: 20, 487: 36,
1619
405: 21, 501: 21, 503: 40,
1620
580: 22, 483: 41, 482: 41,
1625
implements(ITransactionUser)
1627
def __init__(self, portal):
1628
self.portal = portal
1630
self.responseContexts = {}
1631
self.finalResponses = {}
1632
self.registrar = Registrar(portal)
1634
def start(self, transport):
1635
self.transport = transport
1636
self.registrar.start(transport)
1637
self.recordroute = URL(host=transport.host,
1638
port=transport.port, other={'lr':''})
1640
def requestReceived(self, msg, addr):
1642
if msg.uri == self.recordroute:
1643
msg.uri = msg.headers['route'].pop()
1645
if msg.headers.get('route',None):
1646
route = parseAddress(msg.headers['route'][0])[1]
1647
if (route.host in self.transport.hosts and
1648
(route.port or PORT) == self.transport.port):
1649
del msg.headers['route'][0]
1652
if msg.uri.host in self.transport.hosts:
1653
if msg.method == 'REGISTER':
1654
return self.registrar.requestReceived(msg, addr)
1655
elif msg.method == 'OPTIONS' and msg.uri.username is None:
1656
st = ServerTransaction(self.transport, self, msg, addr)
1657
st.messageReceivedFromTU(responseFromRequest(200, msg))
1662
return self.findTargets(msg.uri).addCallback(
1663
self.forwardRequest, msg, st)
1666
if err.check(SIPError):
1667
errcode = err.value.code
1670
if debuggingEnabled:
1671
import pdb; pdb.set_trace()
1674
st.messageReceivedFromTU(
1675
responseFromRequest(errcode, msg))
1677
if msg.method == 'INVITE':
1678
st = ServerInviteTransaction(self.transport, self, msg, addr)
1679
st.messageReceivedFromTU(responseFromRequest(100, msg))
1680
return self.checkInviteAuthorization(msg).addCallback(
1681
_cb, st).addErrback(_eb).addCallback(
1683
elif msg.method == 'CANCEL':
1684
via = parseViaHeader(msg.headers['via'][0])
1685
self.transport.sendResponse(responseFromRequest(200, msg))
1687
for k, t in self.transport.serverTransactions.iteritems():
1688
if (isinstance(t, (ServerTransaction,ServerInviteTransaction))
1689
and (via.branch, via.host, via.port) == k[:3]):
1690
self.cancelPendingClients(t)
1693
self.proxyRequestStatelessly(msg)
1695
elif msg.method == 'ACK':
1696
msg.headers['via'].insert(0,Via(self.transport.host, self.transport.port,
1698
branch=computeBranch(msg)).toString())
1699
self.proxyRequestStatelessly(msg)
1702
st = ServerTransaction(self.transport, self, msg, addr)
1705
if msg.method == 'BYE':
1706
self.untrackSession(msg)
1709
def proxyRequestStatelessly(self, originalMsg):
1711
msg, addr = self.processRouting(originalMsg, addrs[0])
1712
self.transport.sendRequest(msg, (addr.host, addr.port))
1713
self.findTargets(originalMsg.uri).addCallback(_cb)
1715
def findTargets(self, addr):
1716
d = self.portal.login(Preauthenticated(addr.toCredString()),
1719
def lookedUpSuccessful((ifac, contact, logout)):
1720
return defer.maybeDeferred(contact.getRegistrationInfo
1722
lambda x: [i[0] for i in x])
1723
def failedLookup(err):
1724
e = err.trap(NoSuchUser, UnauthorizedLogin)
1725
if e == UnauthorizedLogin:
1727
elif e == NoSuchUser:
1728
raise SIPLookupError(604)
1729
return d.addCallback(lookedUpSuccessful).addErrback(failedLookup)
1731
def forwardRequest(self, targets, originalMsg, st):
1733
if len(targets) == 0:
1734
raise SIPLookupError(480)
1735
for addr in targets:
1736
msg, addr = self.processRouting(originalMsg, addr)
1738
fs.append(self._lookupURI(addr).addCallback(self._forward,msg, st))
1739
return defer.DeferredList(fs)
1741
def processRouting(self, originalMsg, addr):
1743
msg = originalMsg.copy()
1745
if msg.headers.get('max-forwards'):
1746
msg.headers['max-forwards'][0] = str(int(
1747
msg.headers['max-forwards'][0]) - 1)
1749
msg.headers['max-forwards'] = ['70']
1750
msg.headers.setdefault('record-route',
1751
[]).insert(0, self.recordroute.toString())
1752
if msg.headers.get('route', None):
1753
if 'lr' not in parseAddress(msg.headers['route'][0])[1].other:
1754
#more coping with strict routers
1755
msg.headers['route'].append(msg.uri.toString())
1756
msg.uri = parseAddress(msg.headers['route'].pop())[1]
1758
addr = parseAddress(msg.headers['route'][0])[1]
1761
def _forward(self, addresses, msg, st):
1762
for address in addresses:
1764
if msg.method == 'INVITE':
1765
ct = ClientInviteTransaction(self.transport,
1767
timerC = reactor.callLater(181, ct.timeout)
1769
ct = ClientTransaction(self.transport, self, msg, address)
1772
self.responseContexts[ct] = (st, timerC)
1773
self.responseContexts.setdefault(st, []).append(ct)
1774
def _lookupURI(self, userURI):
1775
"""Leave this method: it is hooked by the tests.
1778
if abstract.isIPAddress(userURI.host):
1779
# it is an IP not a hostname
1780
if not userURI.port:
1782
return defer.succeed([(userURI.host, userURI.port)])
1784
if userURI.port is not None:
1785
return defer.succeed([(userURI.host, userURI.port)])
1786
d = client.lookupService('_sip._udp.' + userURI.host)
1787
d.addCallback(self._resolveSRV, userURI)
1790
def _resolveSRV(self, (answers, _, __), userURI):
1793
answers = [(a.payload.priority, str(a.payload.target), a.payload.port) for a in answers]
1795
return [(answer[1], answer[2]) for answer in answers]
1797
#just do an A lookup
1798
return [(userURI.host, 5060)]
1800
def checkInviteAuthorization(self, message):
1801
name, uri, tags = parseAddress(message.headers["to"][0], clean=1)
1802
fromname, fromuri, ignoredTags = parseAddress(message.headers["from"][0], clean=1)
1803
somebodyWasAuthorized = []
1804
def recordIt(oururi, theiruri, method, *extra):
1805
#XXX XXX totally need to check to see if the invite is
1806
#from a registered address
1807
d = self.portal.login(Preauthenticated('%s@%s' % (oururi.username, oururi.host)), None, IContact)
1808
def success((iface, contact, logout), theiruri=theiruri, method=method):
1809
somebodyWasAuthorized.append(contact)
1810
getattr(contact,method)(name, theiruri, *extra)
1812
def ignoreUnauthorized(failure):
1813
failure.trap(UnauthorizedLogin)
1816
return d.addCallbacks(success, ignoreUnauthorized)
1818
# this is a somewhat, uh, "compact" (some would say
1819
# obfuscated) representation of a series of unfortunate
1820
# events, so here is some prose to guide you through it:
1822
# First, we tell the caller (if they're registered with us) that
1823
# they're making a call, to give them the opportunity to record it on
1824
# the server. they don't yet know whether their callee is registered
1825
# or not, and in fact it doesn't matter to them (they _made_ the call
1826
# without such information, after all). A caller can potentially
1827
# cancel the call at this point by raising an exception but I can't
1828
# think of a reason to do that which isn't an error.
1830
# then, we tell the callee (if they're registered with us) that they're
1831
# receiving a call. At this point, they may *decline* the call by
1832
# raising a SIPError of some kind in callIncoming. This is the way
1833
# you'll implement call screening. ( TODO: You *also* ought to be able
1834
# to implement a redirect on an incoming call by raising an appropriate
1835
# SIPError, but there is not currently a way to get the URL for the
1836
# redirect all the way back up the call chain. )
1838
# Before we relay this to the rest of the SIP logic, we make sure that
1839
# at least ONE of the participants was authorized to make or receive
1840
# this call through this server. We don't want to proxy arbitrary
1841
# third-party INVITE requests.
1843
def makeSureSomebodyWasAuthorized(value):
1844
if not somebodyWasAuthorized:
1848
return recordIt(fromuri, uri, 'callOutgoing').addCallback(
1850
recordIt(uri, fromuri, 'callIncoming', caller)
1851
).addCallback(makeSureSomebodyWasAuthorized)
1854
def clientTransactionTerminated(self, ct):
1855
st = self.responseContexts[ct]
1857
if not isinstance(st, ServerTransaction):
1861
cts = self.responseContexts[st]
1863
if ct.mode != "terminated":
1867
if not self.finalResponses.get(st, None):
1868
response = self.chooseFinalResponse(st)
1869
self.finalResponses[st] = response
1870
st.messageReceivedFromTU(response)
1871
del self.responseContexts[st]
1872
if timerC: timerC.cancel()
1874
del self.responseContexts[ct]
1878
def chooseFinalResponse(self, st):
1879
cts = self.responseContexts[st]
1881
responses = [(ct.response and ct.response.code, ct) for ct in cts]
1882
for code, ct in responses:
1883
if code is not None and code >= 200:
1886
assert not noResponses, "BROKEN. chooseFinalResponse was called before any final responses occurred."
1888
prioritizedResponses = []
1889
for code, ct in responses:
1890
prio = responsePriorities.get(code, None)
1892
prioritizedResponses.append((prio, ct.response))
1893
elif 300 <= code < 400:
1894
prioritizedResponses.append((5, ct.response))
1895
elif 500 <= code < 600:
1896
prioritizedResponses.append((42, ct.response))
1898
prioritizedResponses.append((43, ct.response))
1899
prioritizedResponses.sort()
1900
finalResponse = prioritizedResponses[0][1]
1902
#XXX need to process 3xx messages ourselves
1903
#instead of forwarding them
1904
if 300 <= finalResponse.code < 400:
1905
for code, ct in responses:
1906
if 300 <= code < 400:
1907
finalResponse.headers['contact'].extend(
1908
ct.response.headers['contact'])
1909
finalResponse.code = 300
1911
elif finalResponse.code in (401, 407):
1914
for code, ct in responses:
1916
finalResponse.headers['www-authenticate'].extend(
1917
r.headers.get("www-authenticate", []))
1921
finalResponse.headers['proxy-authenticate'].extend(
1922
r.headers.get("proxy-authenticate",[]))
1923
finalResponse.code = 407
1924
return finalResponse
1928
def responseReceived(self, msg, ct=None):
1932
via = msg.headers['via'][0]
1933
msg.headers['via'] = msg.headers['via'][1:]
1935
if len(msg.headers['via']) == 0:
1936
if not msg.headers['cseq'][0].endswith('CANCEL'):
1937
#ignore CANCEL/200s, process the rest
1938
self.processLocalResponse(msg, ct)
1940
if (not ct and msg.headers['cseq'][0].endswith('INVITE')
1941
and 200 <= msg.code < 300):
1942
self.transport.sendResponse(msg)
1945
st, timerC = self.responseContexts.get(ct, (None, None))
1948
self.transport.sendResponse(msg)
1950
if 100 < msg.code < 200:
1951
if isinstance(ct, ClientInviteTransaction):
1953
st.messageReceivedFromTU(msg)
1956
#TODO: Catch 3xx responses, add their redirects to the target set
1957
if 200 <= msg.code < 300:
1958
self.finalResponses[st] = msg
1959
reactor.callLater(0, self.cancelPendingClients, st)
1960
if isinstance(ct, ClientInviteTransaction):
1961
self.trackSession(msg)
1962
elif 600 <= msg.code:
1963
self.cancelPendingClients(st)
1965
#might as well leave the message there, sans our via header
1968
st.messageReceivedFromTU(msg)
1970
def processLocalResponse(self, msg, ct):
1971
if getattr(self.originator, None):
1972
self.originator.responseReceived(msg, ct)
1974
def cancelPendingClients(self, st):
1975
if isinstance(st, ServerInviteTransaction):
1976
cts = self.responseContexts.get(st, [])
1980
def trackSession(self, msg):
1983
def untrackSession(self, msg):
1989
'digest': DigestAuthorizer(),
1991
def __init__(self, portal):
1992
self.portal = portal
1994
def start(self, transport):
1995
self.transport = transport
1997
def getRegistrationInfo(self, url):
1998
#XXX Need to think about impact of all these cred lookups in a
1999
#cluster environment
2000
def _cbRegInfo((i,a,l)):
2001
return a.getRegistrationInfo()
2002
def _ebRegInfo(failure):
2003
failure.trap(UnauthorizedLogin)
2005
return self.portal.login(Preauthenticated('%s@%s' % (url.username,
2007
None, IContact).addCallback(
2008
_cbRegInfo).addErrback(
2012
def requestReceived(self, msg, addr):
2013
st = ServerTransaction(self.transport, self, msg, addr)
2014
if msg.method == "REGISTER":
2015
self.registrate(msg, addr).addCallback(st.messageReceivedFromTU)
2017
st.messageReceivedFromTU(responseFromRequest(501, msg))
2020
def registrate(self, message, addr):
2021
name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
2022
if not message.headers.has_key("authorization"):
2023
creds = credentials.UsernamePassword(toURL.toCredString(),'')
2025
parts = message.headers['authorization'][0].split(None, 1)
2026
a = self.authorizers.get(parts[0].lower())
2028
creds = a.decode(parts[1])
2029
# IGNORE the authorization username - take that, SIP
2030
# configuration UIs!!!
2031
creds.username = toURL.toCredString()
2033
return self.portal.login(creds, None, IContact
2034
).addCallback(self._cbLogin, message, addr
2035
).addErrback(self._ebLogin, message, addr)
2037
def _cbLogin(self, (i, a, l), message, addr):
2038
return self.register(a, message, addr)
2040
def _ebLogin(self, failure, message, addr):
2041
failure.trap(UnauthorizedLogin)
2042
return self.unauthorized(message, addr)
2044
def register(self, avatar, message, addr):
2045
def _cbRegister(regdata, message):
2046
response = responseFromRequest(200, message)
2047
#for old times' sake I will send a separate Expires header
2048
#if there is only one contact
2049
if len(regdata) == 1:
2050
contactURL, expiry = regdata[0]
2051
response.addHeader("contact", contactURL.toString())
2052
response.addHeader("expires", str(expiry))
2054
for contactURL, expiry in regdata:
2055
response.addHeader("contact", "%s;expires=%s" %
2056
(contactURL.toString(), expiry))
2057
response.addHeader("content-length", "0")
2060
def _cbUnregister(regdata, message):
2062
msg = responseFromRequest(200, message)
2063
#More backwards combatibility
2064
if len(regdata) == 1:
2065
contactURL, expiry = regdata[0]
2066
msg.addHeader("contact", contactURL.toString())
2067
msg.addHeader("expires", "0")
2069
for contactURL, expiry in regdata:
2070
msg.addHeader("contact", "%s;expires=%s" %
2071
(contactURL.toString(), 0))
2074
def _ebUnregister(registration, message):
2077
name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
2079
if message.headers.has_key("contact"):
2080
contact = message.headers["contact"][0]
2082
expires = message.headers.get("expires", [None])[0]
2085
return defer.maybeDeferred(avatar.unregisterAllAddresses).addCallback(
2086
_cbUnregister, message
2087
).addErrback(_ebUnregister, message)
2089
name, contactURL, params = parseAddress(contact) #host=addr.host, port=addr.port)
2090
return defer.maybeDeferred(avatar.unregisterAddress,
2091
contactURL).addCallback(
2092
_cbUnregister, message).addErrback(
2093
_ebUnregister, message)
2095
name, contactURL, params = parseAddress(contact)# host=addr.host, port=addr.port)
2096
if contact is not None:
2098
expiresInt = int(expires)
2100
expiresInt = DEFAULT_REGISTRATION_LIFETIME
2101
d = defer.maybeDeferred(avatar.registerAddress,
2102
contactURL, expiresInt)
2104
d = defer.maybeDeferred(avatar.getRegistrationInfo)
2105
d.addCallback(_cbRegister, message).addErrback(self._ebLogin,
2109
def unauthorized(self, message, addr):
2110
# log.msg("Failed registration attempt for %s from %s" %
2111
# (message.headers.get('from'), message.headers.get('contact')))
2112
m = responseFromRequest(401, message)
2113
for (scheme, auth) in self.authorizers.iteritems():
2114
chal = auth.getChallenge(addr)
2116
value = '%s realm="%s"' % (scheme.title(), self.transport.host)
2118
value = '%s %s,realm="%s"' % (scheme.title(), chal,
2119
self.transport.host)
2120
m.headers.setdefault('www-authenticate', []).append(value)
2128
def start(self, transport):
2129
self.transport = transport
2131
def originate(self, fromURL, toURL):
2132
# Call fromURL, wait for pickup, then call toURL and connect
2138
def start(self, transport):
2139
self.transport = transport