Index: third_party/twisted_8_1/twisted/protocols/sip.py |
diff --git a/third_party/twisted_8_1/twisted/protocols/sip.py b/third_party/twisted_8_1/twisted/protocols/sip.py |
deleted file mode 100644 |
index 857c3fbb1dcff2cb8df02bcd73aeab22c01c57af..0000000000000000000000000000000000000000 |
--- a/third_party/twisted_8_1/twisted/protocols/sip.py |
+++ /dev/null |
@@ -1,1190 +0,0 @@ |
-# -*- test-case-name: twisted.test.test_sip -*- |
- |
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories. |
-# See LICENSE for details. |
- |
- |
-"""Session Initialization Protocol. |
- |
-Documented in RFC 2543. |
-[Superceded by 3261] |
-""" |
- |
-# system imports |
-import socket |
-import random |
-import time |
-import md5 |
-import sys |
-from zope.interface import implements, Interface |
- |
-# twisted imports |
-from twisted.python import log, util |
-from twisted.internet import protocol, defer, reactor |
- |
-from twisted import cred |
-import twisted.cred.credentials |
-import twisted.cred.error |
- |
-# sibling imports |
-from twisted.protocols import basic |
- |
-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 seperator ''' |
- return '-'.join([ x.capitalize() for x in s.split('-')]) |
- |
-def unq(s): |
- if s[0] == s[-1] == '"': |
- return s[1:-1] |
- return s |
- |
-def DigestCalcHA1( |
- pszAlg, |
- pszUserName, |
- pszRealm, |
- pszPassword, |
- pszNonce, |
- pszCNonce, |
-): |
- m = md5.md5() |
- m.update(pszUserName) |
- m.update(":") |
- m.update(pszRealm) |
- m.update(":") |
- m.update(pszPassword) |
- HA1 = m.digest() |
- if pszAlg == "md5-sess": |
- m = md5.md5() |
- m.update(HA1) |
- m.update(":") |
- m.update(pszNonce) |
- m.update(":") |
- m.update(pszCNonce) |
- HA1 = m.digest() |
- return HA1.encode('hex') |
- |
-def DigestCalcResponse( |
- HA1, |
- pszNonce, |
- pszNonceCount, |
- pszCNonce, |
- pszQop, |
- pszMethod, |
- pszDigestUri, |
- pszHEntity, |
-): |
- m = md5.md5() |
- m.update(pszMethod) |
- m.update(":") |
- m.update(pszDigestUri) |
- if pszQop == "auth-int": |
- m.update(":") |
- m.update(pszHEntity) |
- HA2 = m.digest().encode('hex') |
- |
- m = md5.md5() |
- m.update(HA1) |
- m.update(":") |
- m.update(pszNonce) |
- m.update(":") |
- if pszNonceCount and pszCNonce: # pszQop: |
- m.update(pszNonceCount) |
- m.update(":") |
- m.update(pszCNonce) |
- m.update(":") |
- m.update(pszQop) |
- m.update(":") |
- m.update(HA2) |
- hash = m.digest().encode('hex') |
- return hash |
- |
-class Via: |
- """A SIP Via header.""" |
- |
- def __init__(self, host, port=PORT, transport="UDP", ttl=None, hidden=False, |
- received=None, rport=None, branch=None, maddr=None): |
- self.transport = transport |
- self.host = host |
- self.port = port |
- self.ttl = ttl |
- self.hidden = hidden |
- self.received = received |
- self.rport = rport |
- self.branch = branch |
- self.maddr = maddr |
- |
- def toString(self): |
- 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", "rport": |
- value = getattr(self, n) |
- if value == True: |
- s += ";" + n |
- elif value != None: |
- s += ";%s=%s" % (n, value) |
- return s |
- |
- |
-def parseViaHeader(value): |
- """Parse a Via header, returning Via class instance.""" |
- 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], True |
- 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 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 = util.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.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: |
- 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 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: |
- # XXX support multi-line headers |
- try: |
- name, value = line.split(":", 1) |
- except ValueError: |
- self.invalidMessage() |
- return |
- self.message.addHeader(name, value.lstrip()) |
- 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.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 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, (srcHost, srcPort)): |
- # RFC 2543 6.40.2, |
- 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)) |
- self.transport.write(message.toString(), (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, 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, (srcHost, srcPort)): |
- """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, thats basically |
- everything. |
- """ |
- 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 BasicAuthorizer: |
- """Authorizer for insecure Basic (base64-encoded plaintext) authentication. |
- |
- This form of authentication is broken and insecure. Do not use it. |
- """ |
- |
- implements(IAuthorizer) |
- |
- def getChallenge(self, peer): |
- return None |
- |
- def decode(self, response): |
- # At least one SIP client improperly pads its Base64 encoded messages |
- for i in range(3): |
- try: |
- creds = (response + ('=' * i)).decode('base64') |
- except: |
- pass |
- else: |
- break |
- else: |
- # Totally bogus |
- raise SIPError(400) |
- p = creds.split(':', 1) |
- if len(p) == 2: |
- return cred.credentials.UsernamePassword(*p) |
- raise SIPError(400) |
- |
- |
-class DigestedCredentials(cred.credentials.UsernameHashedPassword): |
- """Yet Another Simple Digest-MD5 authentication scheme""" |
- |
- def __init__(self, username, fields, challenges): |
- self.username = username |
- self.fields = fields |
- self.challenges = challenges |
- |
- def checkPassword(self, password): |
- method = 'REGISTER' |
- response = self.fields.get('response') |
- uri = self.fields.get('uri') |
- nonce = self.fields.get('nonce') |
- cnonce = self.fields.get('cnonce') |
- nc = self.fields.get('nc') |
- algo = self.fields.get('algorithm', 'MD5') |
- qop = self.fields.get('qop-options', 'auth') |
- opaque = self.fields.get('opaque') |
- |
- if opaque not in self.challenges: |
- return False |
- del self.challenges[opaque] |
- |
- user, domain = self.username.split('@', 1) |
- if uri is None: |
- uri = 'sip:' + domain |
- |
- expected = DigestCalcResponse( |
- DigestCalcHA1(algo, user, domain, password, nonce, cnonce), |
- nonce, nc, cnonce, qop, method, uri, None, |
- ) |
- |
- return expected == response |
- |
-class DigestAuthorizer: |
- CHALLENGE_LIFETIME = 15 |
- |
- implements(IAuthorizer) |
- |
- def __init__(self): |
- self.outstanding = {} |
- |
- def generateNonce(self): |
- c = tuple([random.randrange(sys.maxint) for _ in range(3)]) |
- c = '%d%d%d' % c |
- return c |
- |
- def generateOpaque(self): |
- return str(random.randrange(sys.maxint)) |
- |
- def getChallenge(self, peer): |
- c = self.generateNonce() |
- o = self.generateOpaque() |
- self.outstanding[o] = c |
- return ','.join(( |
- 'nonce="%s"' % c, |
- 'opaque="%s"' % o, |
- 'qop-options="auth"', |
- 'algorithm="MD5"', |
- )) |
- |
- def decode(self, response): |
- response = ' '.join(response.splitlines()) |
- parts = response.split(',') |
- auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1) for p in parts]]) |
- try: |
- username = auth['username'] |
- except KeyError: |
- raise SIPError(401) |
- try: |
- return DigestedCredentials(username, auth, self.outstanding) |
- except: |
- raise SIPError(400) |
- |
- |
-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 = { |
- 'digest': DigestAuthorizer(), |
- } |
- |
- 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. |
- pass |
- |
- def handle_REGISTER_request(self, message, (host, port)): |
- """Handle a registration request. |
- |
- Currently registration is not proxied. |
- """ |
- 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 not message.headers.has_key("authorization"): |
- 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 self.authorizers.iteritems(): |
- 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. |
- 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 message.headers.has_key("contact"): |
- 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 |
- |
- |
-class InMemoryRegistry: |
- """A simplistic registry for a specific domain.""" |
- |
- implements(IRegistry, ILocator) |
- |
- 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 self.users.has_key(userURI.username): |
- 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 self.users.has_key(userURI.username): |
- 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 self.users.has_key(logicalURL.username): |
- 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) |