1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294 |
- # -*- test-case-name: twisted.test.test_sip -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Session Initialization Protocol.
- Documented in RFC 2543.
- [Superseded by 3261]
- """
- import socket
- import time
- import warnings
- from zope.interface import implementer, Interface
- from collections import OrderedDict
- from twisted import cred
- from twisted.internet import protocol, defer, reactor
- from twisted.protocols import basic
- from twisted.python import log
- from twisted.python.compat import _PY3, iteritems, unicode
- PORT = 5060
- # SIP headers have short forms
- shortHeaders = {"call-id": "i",
- "contact": "m",
- "content-encoding": "e",
- "content-length": "l",
- "content-type": "c",
- "from": "f",
- "subject": "s",
- "to": "t",
- "via": "v",
- }
- longHeaders = {}
- for k, v in shortHeaders.items():
- longHeaders[v] = k
- del k, v
- statusCodes = {
- 100: "Trying",
- 180: "Ringing",
- 181: "Call Is Being Forwarded",
- 182: "Queued",
- 183: "Session Progress",
- 200: "OK",
- 300: "Multiple Choices",
- 301: "Moved Permanently",
- 302: "Moved Temporarily",
- 303: "See Other",
- 305: "Use Proxy",
- 380: "Alternative Service",
- 400: "Bad Request",
- 401: "Unauthorized",
- 402: "Payment Required",
- 403: "Forbidden",
- 404: "Not Found",
- 405: "Method Not Allowed",
- 406: "Not Acceptable",
- 407: "Proxy Authentication Required",
- 408: "Request Timeout",
- 409: "Conflict", # Not in RFC3261
- 410: "Gone",
- 411: "Length Required", # Not in RFC3261
- 413: "Request Entity Too Large",
- 414: "Request-URI Too Large",
- 415: "Unsupported Media Type",
- 416: "Unsupported URI Scheme",
- 420: "Bad Extension",
- 421: "Extension Required",
- 423: "Interval Too Brief",
- 480: "Temporarily Unavailable",
- 481: "Call/Transaction Does Not Exist",
- 482: "Loop Detected",
- 483: "Too Many Hops",
- 484: "Address Incomplete",
- 485: "Ambiguous",
- 486: "Busy Here",
- 487: "Request Terminated",
- 488: "Not Acceptable Here",
- 491: "Request Pending",
- 493: "Undecipherable",
- 500: "Internal Server Error",
- 501: "Not Implemented",
- 502: "Bad Gateway", # No donut
- 503: "Service Unavailable",
- 504: "Server Time-out",
- 505: "SIP Version not supported",
- 513: "Message Too Large",
- 600: "Busy Everywhere",
- 603: "Decline",
- 604: "Does not exist anywhere",
- 606: "Not Acceptable",
- }
- specialCases = {
- 'cseq': 'CSeq',
- 'call-id': 'Call-ID',
- 'www-authenticate': 'WWW-Authenticate',
- }
- def dashCapitalize(s):
- """
- Capitalize a string, making sure to treat '-' as a word separator
- """
- return '-'.join([ x.capitalize() for x in s.split('-')])
- def unq(s):
- if s[0] == s[-1] == '"':
- return s[1:-1]
- return s
- _absent = object()
- class Via(object):
- """
- A L{Via} is a SIP Via header, representing a segment of the path taken by
- the request.
- See RFC 3261, sections 8.1.1.7, 18.2.2, and 20.42.
- @ivar transport: Network protocol used for this leg. (Probably either "TCP"
- or "UDP".)
- @type transport: C{str}
- @ivar branch: Unique identifier for this request.
- @type branch: C{str}
- @ivar host: Hostname or IP for this leg.
- @type host: C{str}
- @ivar port: Port used for this leg.
- @type port C{int}, or None.
- @ivar rportRequested: Whether to request RFC 3581 client processing or not.
- @type rportRequested: C{bool}
- @ivar rportValue: Servers wishing to honor requests for RFC 3581 processing
- should set this parameter to the source port the request was received
- from.
- @type rportValue: C{int}, or None.
- @ivar ttl: Time-to-live for requests on multicast paths.
- @type ttl: C{int}, or None.
- @ivar maddr: The destination multicast address, if any.
- @type maddr: C{str}, or None.
- @ivar hidden: Obsolete in SIP 2.0.
- @type hidden: C{bool}
- @ivar otherParams: Any other parameters in the header.
- @type otherParams: C{dict}
- """
- def __init__(self, host, port=PORT, transport="UDP", ttl=None,
- hidden=False, received=None, rport=_absent, branch=None,
- maddr=None, **kw):
- """
- Set parameters of this Via header. All arguments correspond to
- attributes of the same name.
- To maintain compatibility with old SIP
- code, the 'rport' argument is used to determine the values of
- C{rportRequested} and C{rportValue}. If None, C{rportRequested} is set
- to True. (The deprecated method for doing this is to pass True.) If an
- integer, C{rportValue} is set to the given value.
- Any arguments not explicitly named here are collected into the
- C{otherParams} dict.
- """
- self.transport = transport
- self.host = host
- self.port = port
- self.ttl = ttl
- self.hidden = hidden
- self.received = received
- if rport is True:
- warnings.warn(
- "rport=True is deprecated since Twisted 9.0.",
- DeprecationWarning,
- stacklevel=2)
- self.rportValue = None
- self.rportRequested = True
- elif rport is None:
- self.rportValue = None
- self.rportRequested = True
- elif rport is _absent:
- self.rportValue = None
- self.rportRequested = False
- else:
- self.rportValue = rport
- self.rportRequested = False
- self.branch = branch
- self.maddr = maddr
- self.otherParams = kw
- def _getrport(self):
- """
- Returns the rport value expected by the old SIP code.
- """
- if self.rportRequested == True:
- return True
- elif self.rportValue is not None:
- return self.rportValue
- else:
- return None
- def _setrport(self, newRPort):
- """
- L{Base._fixupNAT} sets C{rport} directly, so this method sets
- C{rportValue} based on that.
- @param newRPort: The new rport value.
- @type newRPort: C{int}
- """
- self.rportValue = newRPort
- self.rportRequested = False
- rport = property(_getrport, _setrport)
- def toString(self):
- """
- Serialize this header for use in a request or response.
- """
- s = "SIP/2.0/%s %s:%s" % (self.transport, self.host, self.port)
- if self.hidden:
- s += ";hidden"
- for n in "ttl", "branch", "maddr", "received":
- value = getattr(self, n)
- if value is not None:
- s += ";%s=%s" % (n, value)
- if self.rportRequested:
- s += ";rport"
- elif self.rportValue is not None:
- s += ";rport=%s" % (self.rport,)
- etc = sorted(self.otherParams.items())
- for k, v in etc:
- if v is None:
- s += ";" + k
- else:
- s += ";%s=%s" % (k, v)
- return s
- def parseViaHeader(value):
- """
- Parse a Via header.
- @return: The parsed version of this header.
- @rtype: L{Via}
- """
- parts = value.split(";")
- sent, params = parts[0], parts[1:]
- protocolinfo, by = sent.split(" ", 1)
- by = by.strip()
- result = {}
- pname, pversion, transport = protocolinfo.split("/")
- if pname != "SIP" or pversion != "2.0":
- raise ValueError("wrong protocol or version: %r" % (value,))
- result["transport"] = transport
- if ":" in by:
- host, port = by.split(":")
- result["port"] = int(port)
- result["host"] = host
- else:
- result["host"] = by
- for p in params:
- # It's the comment-striping dance!
- p = p.strip().split(" ", 1)
- if len(p) == 1:
- p, comment = p[0], ""
- else:
- p, comment = p
- if p == "hidden":
- result["hidden"] = True
- continue
- parts = p.split("=", 1)
- if len(parts) == 1:
- name, value = parts[0], None
- else:
- name, value = parts
- if name in ("rport", "ttl"):
- value = int(value)
- result[name] = value
- return Via(**result)
- class URL:
- """
- A SIP URL.
- """
- def __init__(self, host, username=None, password=None, port=None,
- transport=None, usertype=None, method=None,
- ttl=None, maddr=None, tag=None, other=None, headers=None):
- self.username = username
- self.host = host
- self.password = password
- self.port = port
- self.transport = transport
- self.usertype = usertype
- self.method = method
- self.tag = tag
- self.ttl = ttl
- self.maddr = maddr
- if other == None:
- self.other = []
- else:
- self.other = other
- if headers == None:
- self.headers = {}
- else:
- self.headers = headers
- def toString(self):
- l = []; w = l.append
- w("sip:")
- if self.username != None:
- w(self.username)
- if self.password != None:
- w(":%s" % self.password)
- w("@")
- w(self.host)
- if self.port != None:
- w(":%d" % self.port)
- if self.usertype != None:
- w(";user=%s" % self.usertype)
- for n in ("transport", "ttl", "maddr", "method", "tag"):
- v = getattr(self, n)
- if v != None:
- w(";%s=%s" % (n, v))
- for v in self.other:
- w(";%s" % v)
- if self.headers:
- w("?")
- w("&".join([("%s=%s" % (specialCases.get(h) or dashCapitalize(h), v)) for (h, v) in self.headers.items()]))
- return "".join(l)
- def __str__(self):
- return self.toString()
- def __repr__(self):
- return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host, self.port, self.transport)
- def parseURL(url, host=None, port=None):
- """
- Return string into URL object.
- URIs are of form 'sip:user@example.com'.
- """
- d = {}
- if not url.startswith("sip:"):
- raise ValueError("unsupported scheme: " + url[:4])
- parts = url[4:].split(";")
- userdomain, params = parts[0], parts[1:]
- udparts = userdomain.split("@", 1)
- if len(udparts) == 2:
- userpass, hostport = udparts
- upparts = userpass.split(":", 1)
- if len(upparts) == 1:
- d["username"] = upparts[0]
- else:
- d["username"] = upparts[0]
- d["password"] = upparts[1]
- else:
- hostport = udparts[0]
- hpparts = hostport.split(":", 1)
- if len(hpparts) == 1:
- d["host"] = hpparts[0]
- else:
- d["host"] = hpparts[0]
- d["port"] = int(hpparts[1])
- if host != None:
- d["host"] = host
- if port != None:
- d["port"] = port
- for p in params:
- if p == params[-1] and "?" in p:
- d["headers"] = h = {}
- p, headers = p.split("?", 1)
- for header in headers.split("&"):
- k, v = header.split("=")
- h[k] = v
- nv = p.split("=", 1)
- if len(nv) == 1:
- d.setdefault("other", []).append(p)
- continue
- name, value = nv
- if name == "user":
- d["usertype"] = value
- elif name in ("transport", "ttl", "maddr", "method", "tag"):
- if name == "ttl":
- value = int(value)
- d[name] = value
- else:
- d.setdefault("other", []).append(p)
- return URL(**d)
- def cleanRequestURL(url):
- """
- Clean a URL from a Request line.
- """
- url.transport = None
- url.maddr = None
- url.ttl = None
- url.headers = {}
- def parseAddress(address, host=None, port=None, clean=0):
- """
- Return (name, uri, params) for From/To/Contact header.
- @param clean: remove unnecessary info, usually for From and To headers.
- """
- address = address.strip()
- # Simple 'sip:foo' case
- if address.startswith("sip:"):
- return "", parseURL(address, host=host, port=port), {}
- params = {}
- name, url = address.split("<", 1)
- name = name.strip()
- if name.startswith('"'):
- name = name[1:]
- if name.endswith('"'):
- name = name[:-1]
- url, paramstring = url.split(">", 1)
- url = parseURL(url, host=host, port=port)
- paramstring = paramstring.strip()
- if paramstring:
- for l in paramstring.split(";"):
- if not l:
- continue
- k, v = l.split("=")
- params[k] = v
- if clean:
- # RFC 2543 6.21
- url.ttl = None
- url.headers = {}
- url.transport = None
- url.maddr = None
- return name, url, params
- class SIPError(Exception):
- def __init__(self, code, phrase=None):
- if phrase is None:
- phrase = statusCodes[code]
- Exception.__init__(self, "SIP error (%d): %s" % (code, phrase))
- self.code = code
- self.phrase = phrase
- class RegistrationError(SIPError):
- """
- Registration was not possible.
- """
- class Message:
- """
- A SIP message.
- """
- length = None
- def __init__(self):
- self.headers = OrderedDict() # Map name to list of values
- self.body = ""
- self.finished = 0
- def addHeader(self, name, value):
- name = name.lower()
- name = longHeaders.get(name, name)
- if name == "content-length":
- self.length = int(value)
- self.headers.setdefault(name,[]).append(value)
- def bodyDataReceived(self, data):
- self.body += data
- def creationFinished(self):
- if (self.length != None) and (self.length != len(self.body)):
- raise ValueError("wrong body length")
- self.finished = 1
- def toString(self):
- s = "%s\r\n" % self._getHeaderLine()
- for n, vs in self.headers.items():
- for v in vs:
- s += "%s: %s\r\n" % (specialCases.get(n) or dashCapitalize(n), v)
- s += "\r\n"
- s += self.body
- return s
- def _getHeaderLine(self):
- raise NotImplementedError
- class Request(Message):
- """
- A Request for a URI
- """
- def __init__(self, method, uri, version="SIP/2.0"):
- Message.__init__(self)
- self.method = method
- if isinstance(uri, URL):
- self.uri = uri
- else:
- self.uri = parseURL(uri)
- cleanRequestURL(self.uri)
- def __repr__(self):
- return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toString())
- def _getHeaderLine(self):
- return "%s %s SIP/2.0" % (self.method, self.uri.toString())
- class Response(Message):
- """
- A Response to a URI Request
- """
- def __init__(self, code, phrase=None, version="SIP/2.0"):
- Message.__init__(self)
- self.code = code
- if phrase == None:
- phrase = statusCodes[code]
- self.phrase = phrase
- def __repr__(self):
- return "<SIP Response %d:%s>" % (id(self), self.code)
- def _getHeaderLine(self):
- return "SIP/2.0 %s %s" % (self.code, self.phrase)
- class MessagesParser(basic.LineReceiver):
- """
- A SIP messages parser.
- Expects dataReceived, dataDone repeatedly,
- in that order. Shouldn't be connected to actual transport.
- """
- version = "SIP/2.0"
- acceptResponses = 1
- acceptRequests = 1
- state = "firstline" # Or "headers", "body" or "invalid"
- debug = 0
- def __init__(self, messageReceivedCallback):
- self.messageReceived = messageReceivedCallback
- self.reset()
- def reset(self, remainingData=""):
- self.state = "firstline"
- self.length = None # Body length
- self.bodyReceived = 0 # How much of the body we received
- self.message = None
- self.header = None
- self.setLineMode(remainingData)
- def invalidMessage(self):
- self.state = "invalid"
- self.setRawMode()
- def dataDone(self):
- """
- Clear out any buffered data that may be hanging around.
- """
- self.clearLineBuffer()
- if self.state == "firstline":
- return
- if self.state != "body":
- self.reset()
- return
- if self.length == None:
- # No content-length header, so end of data signals message done
- self.messageDone()
- elif self.length < self.bodyReceived:
- # Aborted in the middle
- self.reset()
- else:
- # We have enough data and message wasn't finished? something is wrong
- raise RuntimeError("this should never happen")
- def dataReceived(self, data):
- try:
- if isinstance(data, unicode):
- data = data.encode("utf-8")
- basic.LineReceiver.dataReceived(self, data)
- except:
- log.err()
- self.invalidMessage()
- def handleFirstLine(self, line):
- """
- Expected to create self.message.
- """
- raise NotImplementedError
- def lineLengthExceeded(self, line):
- self.invalidMessage()
- def lineReceived(self, line):
- if _PY3 and isinstance(line, bytes):
- line = line.decode("utf-8")
- if self.state == "firstline":
- while line.startswith("\n") or line.startswith("\r"):
- line = line[1:]
- if not line:
- return
- try:
- a, b, c = line.split(" ", 2)
- except ValueError:
- self.invalidMessage()
- return
- if a == "SIP/2.0" and self.acceptResponses:
- # Response
- try:
- code = int(b)
- except ValueError:
- self.invalidMessage()
- return
- self.message = Response(code, c)
- elif c == "SIP/2.0" and self.acceptRequests:
- self.message = Request(a, b)
- else:
- self.invalidMessage()
- return
- self.state = "headers"
- return
- else:
- assert self.state == "headers"
- if line:
- # Multiline header
- if line.startswith(" ") or line.startswith("\t"):
- name, value = self.header
- self.header = name, (value + line.lstrip())
- else:
- # New header
- if self.header:
- self.message.addHeader(*self.header)
- self.header = None
- try:
- name, value = line.split(":", 1)
- except ValueError:
- self.invalidMessage()
- return
- self.header = name, value.lstrip()
- # XXX we assume content-length won't be multiline
- if name.lower() == "content-length":
- try:
- self.length = int(value.lstrip())
- except ValueError:
- self.invalidMessage()
- return
- else:
- # CRLF, we now have message body until self.length bytes,
- # or if no length was given, until there is no more data
- # from the connection sending us data.
- self.state = "body"
- if self.header:
- self.message.addHeader(*self.header)
- self.header = None
- if self.length == 0:
- self.messageDone()
- return
- self.setRawMode()
- def messageDone(self, remainingData=""):
- assert self.state == "body"
- self.message.creationFinished()
- self.messageReceived(self.message)
- self.reset(remainingData)
- def rawDataReceived(self, data):
- assert self.state in ("body", "invalid")
- if _PY3 and isinstance(data, bytes):
- data = data.decode("utf-8")
- if self.state == "invalid":
- return
- if self.length == None:
- self.message.bodyDataReceived(data)
- else:
- dataLen = len(data)
- expectedLen = self.length - self.bodyReceived
- if dataLen > expectedLen:
- self.message.bodyDataReceived(data[:expectedLen])
- self.messageDone(data[expectedLen:])
- return
- else:
- self.bodyReceived += dataLen
- self.message.bodyDataReceived(data)
- if self.bodyReceived == self.length:
- self.messageDone()
- class Base(protocol.DatagramProtocol):
- """
- Base class for SIP clients and servers.
- """
- PORT = PORT
- debug = False
- def __init__(self):
- self.messages = []
- self.parser = MessagesParser(self.addMessage)
- def addMessage(self, msg):
- self.messages.append(msg)
- def datagramReceived(self, data, addr):
- self.parser.dataReceived(data)
- self.parser.dataDone()
- for m in self.messages:
- self._fixupNAT(m, addr)
- if self.debug:
- log.msg("Received %r from %r" % (m.toString(), addr))
- if isinstance(m, Request):
- self.handle_request(m, addr)
- else:
- self.handle_response(m, addr)
- self.messages[:] = []
- def _fixupNAT(self, message, sourcePeer):
- # RFC 2543 6.40.2,
- (srcHost, srcPort) = sourcePeer
- senderVia = parseViaHeader(message.headers["via"][0])
- if senderVia.host != srcHost:
- senderVia.received = srcHost
- if senderVia.port != srcPort:
- senderVia.rport = srcPort
- message.headers["via"][0] = senderVia.toString()
- elif senderVia.rport == True:
- senderVia.received = srcHost
- senderVia.rport = srcPort
- message.headers["via"][0] = senderVia.toString()
- def deliverResponse(self, responseMessage):
- """
- Deliver response.
- Destination is based on topmost Via header.
- """
- destVia = parseViaHeader(responseMessage.headers["via"][0])
- # XXX we don't do multicast yet
- host = destVia.received or destVia.host
- port = destVia.rport or destVia.port or self.PORT
- destAddr = URL(host=host, port=port)
- self.sendMessage(destAddr, responseMessage)
- def responseFromRequest(self, code, request):
- """
- Create a response to a request message.
- """
- response = Response(code)
- for name in ("via", "to", "from", "call-id", "cseq"):
- response.headers[name] = request.headers.get(name, [])[:]
- return response
- def sendMessage(self, destURL, message):
- """
- Send a message.
- @param destURL: C{URL}. This should be a *physical* URL, not a logical one.
- @param message: The message to send.
- """
- if destURL.transport not in ("udp", None):
- raise RuntimeError("only UDP currently supported")
- if self.debug:
- log.msg("Sending %r to %r" % (message.toString(), destURL))
- data = message.toString()
- if isinstance(data, unicode):
- data = data.encode("utf-8")
- self.transport.write(data, (destURL.host, destURL.port or self.PORT))
- def handle_request(self, message, addr):
- """
- Override to define behavior for requests received
- @type message: C{Message}
- @type addr: C{tuple}
- """
- raise NotImplementedError
- def handle_response(self, message, addr):
- """
- Override to define behavior for responses received.
- @type message: C{Message}
- @type addr: C{tuple}
- """
- raise NotImplementedError
- class IContact(Interface):
- """
- A user of a registrar or proxy
- """
- class Registration:
- def __init__(self, secondsToExpiry, contactURL):
- self.secondsToExpiry = secondsToExpiry
- self.contactURL = contactURL
- class IRegistry(Interface):
- """
- Allows registration of logical->physical URL mapping.
- """
- def registerAddress(domainURL, logicalURL, physicalURL):
- """
- Register the physical address of a logical URL.
- @return: Deferred of C{Registration} or failure with RegistrationError.
- """
- def unregisterAddress(domainURL, logicalURL, physicalURL):
- """
- Unregister the physical address of a logical URL.
- @return: Deferred of C{Registration} or failure with RegistrationError.
- """
- def getRegistrationInfo(logicalURL):
- """
- Get registration info for logical URL.
- @return: Deferred of C{Registration} object or failure of LookupError.
- """
- class ILocator(Interface):
- """
- Allow looking up physical address for logical URL.
- """
- def getAddress(logicalURL):
- """
- Return physical URL of server for logical URL of user.
- @param logicalURL: a logical C{URL}.
- @return: Deferred which becomes URL or fails with LookupError.
- """
- class Proxy(Base):
- """
- SIP proxy.
- """
- PORT = PORT
- locator = None # Object implementing ILocator
- def __init__(self, host=None, port=PORT):
- """
- Create new instance.
- @param host: our hostname/IP as set in Via headers.
- @param port: our port as set in Via headers.
- """
- self.host = host or socket.getfqdn()
- self.port = port
- Base.__init__(self)
- def getVia(self):
- """
- Return value of Via header for this proxy.
- """
- return Via(host=self.host, port=self.port)
- def handle_request(self, message, addr):
- # Send immediate 100/trying message before processing
- #self.deliverResponse(self.responseFromRequest(100, message))
- f = getattr(self, "handle_%s_request" % message.method, None)
- if f is None:
- f = self.handle_request_default
- try:
- d = f(message, addr)
- except SIPError as e:
- self.deliverResponse(self.responseFromRequest(e.code, message))
- except:
- log.err()
- self.deliverResponse(self.responseFromRequest(500, message))
- else:
- if d is not None:
- d.addErrback(lambda e:
- self.deliverResponse(self.responseFromRequest(e.code, message))
- )
- def handle_request_default(self, message, sourcePeer):
- """
- Default request handler.
- Default behaviour for OPTIONS and unknown methods for proxies
- is to forward message on to the client.
- Since at the moment we are stateless proxy, that's basically
- everything.
- """
- (srcHost, srcPort) = sourcePeer
- def _mungContactHeader(uri, message):
- message.headers['contact'][0] = uri.toString()
- return self.sendMessage(uri, message)
- viaHeader = self.getVia()
- if viaHeader.toString() in message.headers["via"]:
- # Must be a loop, so drop message
- log.msg("Dropping looped message.")
- return
- message.headers["via"].insert(0, viaHeader.toString())
- name, uri, tags = parseAddress(message.headers["to"][0], clean=1)
- # This is broken and needs refactoring to use cred
- d = self.locator.getAddress(uri)
- d.addCallback(self.sendMessage, message)
- d.addErrback(self._cantForwardRequest, message)
- def _cantForwardRequest(self, error, message):
- error.trap(LookupError)
- del message.headers["via"][0] # This'll be us
- self.deliverResponse(self.responseFromRequest(404, message))
- def deliverResponse(self, responseMessage):
- """
- Deliver response.
- Destination is based on topmost Via header.
- """
- destVia = parseViaHeader(responseMessage.headers["via"][0])
- # XXX we don't do multicast yet
- host = destVia.received or destVia.host
- port = destVia.rport or destVia.port or self.PORT
- destAddr = URL(host=host, port=port)
- self.sendMessage(destAddr, responseMessage)
- def responseFromRequest(self, code, request):
- """
- Create a response to a request message.
- """
- response = Response(code)
- for name in ("via", "to", "from", "call-id", "cseq"):
- response.headers[name] = request.headers.get(name, [])[:]
- return response
- def handle_response(self, message, addr):
- """
- Default response handler.
- """
- v = parseViaHeader(message.headers["via"][0])
- if (v.host, v.port) != (self.host, self.port):
- # We got a message not intended for us?
- # XXX note this check breaks if we have multiple external IPs
- # yay for suck protocols
- log.msg("Dropping incorrectly addressed message")
- return
- del message.headers["via"][0]
- if not message.headers["via"]:
- # This message is addressed to us
- self.gotResponse(message, addr)
- return
- self.deliverResponse(message)
- def gotResponse(self, message, addr):
- """
- Called with responses that are addressed at this server.
- """
- pass
- class IAuthorizer(Interface):
- def getChallenge(peer):
- """
- Generate a challenge the client may respond to.
- @type peer: C{tuple}
- @param peer: The client's address
- @rtype: C{str}
- @return: The challenge string
- """
- def decode(response):
- """
- Create a credentials object from the given response.
- @type response: C{str}
- """
- class RegisterProxy(Proxy):
- """
- A proxy that allows registration for a specific domain.
- Unregistered users won't be handled.
- """
- portal = None
- registry = None # Should implement IRegistry
- authorizers = {}
- def __init__(self, *args, **kw):
- Proxy.__init__(self, *args, **kw)
- self.liveChallenges = {}
- def handle_ACK_request(self, message, host_port):
- # XXX
- # ACKs are a client's way of indicating they got the last message
- # Responding to them is not a good idea.
- # However, we should keep track of terminal messages and re-transmit
- # if no ACK is received.
- (host, port) = host_port
- pass
- def handle_REGISTER_request(self, message, host_port):
- """
- Handle a registration request.
- Currently registration is not proxied.
- """
- (host, port) = host_port
- if self.portal is None:
- # There is no portal. Let anyone in.
- self.register(message, host, port)
- else:
- # There is a portal. Check for credentials.
- if "authorization" not in message.headers:
- return self.unauthorized(message, host, port)
- else:
- return self.login(message, host, port)
- def unauthorized(self, message, host, port):
- m = self.responseFromRequest(401, message)
- for (scheme, auth) in iteritems(self.authorizers):
- chal = auth.getChallenge((host, port))
- if chal is None:
- value = '%s realm="%s"' % (scheme.title(), self.host)
- else:
- value = '%s %s,realm="%s"' % (scheme.title(), chal, self.host)
- m.headers.setdefault('www-authenticate', []).append(value)
- self.deliverResponse(m)
- def login(self, message, host, port):
- parts = message.headers['authorization'][0].split(None, 1)
- a = self.authorizers.get(parts[0].lower())
- if a:
- try:
- c = a.decode(parts[1])
- except SIPError:
- raise
- except:
- log.err()
- self.deliverResponse(self.responseFromRequest(500, message))
- else:
- c.username += '@' + self.host
- self.portal.login(c, None, IContact
- ).addCallback(self._cbLogin, message, host, port
- ).addErrback(self._ebLogin, message, host, port
- ).addErrback(log.err
- )
- else:
- self.deliverResponse(self.responseFromRequest(501, message))
- def _cbLogin(self, i_a_l, message, host, port):
- # It's stateless, matey. What a joke.
- (i, a, l) = i_a_l
- self.register(message, host, port)
- def _ebLogin(self, failure, message, host, port):
- failure.trap(cred.error.UnauthorizedLogin)
- self.unauthorized(message, host, port)
- def register(self, message, host, port):
- """
- Allow all users to register
- """
- name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
- contact = None
- if "contact" in message.headers:
- contact = message.headers["contact"][0]
- if message.headers.get("expires", [None])[0] == "0":
- self.unregister(message, toURL, contact)
- else:
- # XXX Check expires on appropriate URL, and pass it to registry
- # instead of having registry hardcode it.
- if contact is not None:
- name, contactURL, params = parseAddress(contact, host=host, port=port)
- d = self.registry.registerAddress(message.uri, toURL, contactURL)
- else:
- d = self.registry.getRegistrationInfo(toURL)
- d.addCallbacks(self._cbRegister, self._ebRegister,
- callbackArgs=(message,),
- errbackArgs=(message,)
- )
- def _cbRegister(self, registration, message):
- response = self.responseFromRequest(200, message)
- if registration.contactURL != None:
- response.addHeader("contact", registration.contactURL.toString())
- response.addHeader("expires", "%d" % registration.secondsToExpiry)
- response.addHeader("content-length", "0")
- self.deliverResponse(response)
- def _ebRegister(self, error, message):
- error.trap(RegistrationError, LookupError)
- # XXX return error message, and alter tests to deal with
- # this, currently tests assume no message sent on failure
- def unregister(self, message, toURL, contact):
- try:
- expires = int(message.headers["expires"][0])
- except ValueError:
- self.deliverResponse(self.responseFromRequest(400, message))
- else:
- if expires == 0:
- if contact == "*":
- contactURL = "*"
- else:
- name, contactURL, params = parseAddress(contact)
- d = self.registry.unregisterAddress(message.uri, toURL, contactURL)
- d.addCallback(self._cbUnregister, message
- ).addErrback(self._ebUnregister, message
- )
- def _cbUnregister(self, registration, message):
- msg = self.responseFromRequest(200, message)
- msg.headers.setdefault('contact', []).append(registration.contactURL.toString())
- msg.addHeader("expires", "0")
- self.deliverResponse(msg)
- def _ebUnregister(self, registration, message):
- pass
- @implementer(IRegistry, ILocator)
- class InMemoryRegistry:
- """
- A simplistic registry for a specific domain.
- """
- def __init__(self, domain):
- self.domain = domain # The domain we handle registration for
- self.users = {} # Map username to (IDelayedCall for expiry, address URI)
- def getAddress(self, userURI):
- if userURI.host != self.domain:
- return defer.fail(LookupError("unknown domain"))
- if userURI.username in self.users:
- dc, url = self.users[userURI.username]
- return defer.succeed(url)
- else:
- return defer.fail(LookupError("no such user"))
- def getRegistrationInfo(self, userURI):
- if userURI.host != self.domain:
- return defer.fail(LookupError("unknown domain"))
- if userURI.username in self.users:
- dc, url = self.users[userURI.username]
- return defer.succeed(Registration(int(dc.getTime() - time.time()), url))
- else:
- return defer.fail(LookupError("no such user"))
- def _expireRegistration(self, username):
- try:
- dc, url = self.users[username]
- except KeyError:
- return defer.fail(LookupError("no such user"))
- else:
- dc.cancel()
- del self.users[username]
- return defer.succeed(Registration(0, url))
- def registerAddress(self, domainURL, logicalURL, physicalURL):
- if domainURL.host != self.domain:
- log.msg("Registration for domain we don't handle.")
- return defer.fail(RegistrationError(404))
- if logicalURL.host != self.domain:
- log.msg("Registration for domain we don't handle.")
- return defer.fail(RegistrationError(404))
- if logicalURL.username in self.users:
- dc, old = self.users[logicalURL.username]
- dc.reset(3600)
- else:
- dc = reactor.callLater(3600, self._expireRegistration, logicalURL.username)
- log.msg("Registered %s at %s" % (logicalURL.toString(), physicalURL.toString()))
- self.users[logicalURL.username] = (dc, physicalURL)
- return defer.succeed(Registration(int(dc.getTime() - time.time()), physicalURL))
- def unregisterAddress(self, domainURL, logicalURL, physicalURL):
- return self._expireRegistration(logicalURL.username)
|