| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.test.test_sip -*- | |
| 2 | |
| 3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 4 # See LICENSE for details. | |
| 5 | |
| 6 | |
| 7 """Session Initialization Protocol. | |
| 8 | |
| 9 Documented in RFC 2543. | |
| 10 [Superceded by 3261] | |
| 11 """ | |
| 12 | |
| 13 # system imports | |
| 14 import socket | |
| 15 import random | |
| 16 import time | |
| 17 import md5 | |
| 18 import sys | |
| 19 from zope.interface import implements, Interface | |
| 20 | |
| 21 # twisted imports | |
| 22 from twisted.python import log, util | |
| 23 from twisted.internet import protocol, defer, reactor | |
| 24 | |
| 25 from twisted import cred | |
| 26 import twisted.cred.credentials | |
| 27 import twisted.cred.error | |
| 28 | |
| 29 # sibling imports | |
| 30 from twisted.protocols import basic | |
| 31 | |
| 32 PORT = 5060 | |
| 33 | |
| 34 # SIP headers have short forms | |
| 35 shortHeaders = {"call-id": "i", | |
| 36 "contact": "m", | |
| 37 "content-encoding": "e", | |
| 38 "content-length": "l", | |
| 39 "content-type": "c", | |
| 40 "from": "f", | |
| 41 "subject": "s", | |
| 42 "to": "t", | |
| 43 "via": "v", | |
| 44 } | |
| 45 | |
| 46 longHeaders = {} | |
| 47 for k, v in shortHeaders.items(): | |
| 48 longHeaders[v] = k | |
| 49 del k, v | |
| 50 | |
| 51 statusCodes = { | |
| 52 100: "Trying", | |
| 53 180: "Ringing", | |
| 54 181: "Call Is Being Forwarded", | |
| 55 182: "Queued", | |
| 56 183: "Session Progress", | |
| 57 | |
| 58 200: "OK", | |
| 59 | |
| 60 300: "Multiple Choices", | |
| 61 301: "Moved Permanently", | |
| 62 302: "Moved Temporarily", | |
| 63 303: "See Other", | |
| 64 305: "Use Proxy", | |
| 65 380: "Alternative Service", | |
| 66 | |
| 67 400: "Bad Request", | |
| 68 401: "Unauthorized", | |
| 69 402: "Payment Required", | |
| 70 403: "Forbidden", | |
| 71 404: "Not Found", | |
| 72 405: "Method Not Allowed", | |
| 73 406: "Not Acceptable", | |
| 74 407: "Proxy Authentication Required", | |
| 75 408: "Request Timeout", | |
| 76 409: "Conflict", # Not in RFC3261 | |
| 77 410: "Gone", | |
| 78 411: "Length Required", # Not in RFC3261 | |
| 79 413: "Request Entity Too Large", | |
| 80 414: "Request-URI Too Large", | |
| 81 415: "Unsupported Media Type", | |
| 82 416: "Unsupported URI Scheme", | |
| 83 420: "Bad Extension", | |
| 84 421: "Extension Required", | |
| 85 423: "Interval Too Brief", | |
| 86 480: "Temporarily Unavailable", | |
| 87 481: "Call/Transaction Does Not Exist", | |
| 88 482: "Loop Detected", | |
| 89 483: "Too Many Hops", | |
| 90 484: "Address Incomplete", | |
| 91 485: "Ambiguous", | |
| 92 486: "Busy Here", | |
| 93 487: "Request Terminated", | |
| 94 488: "Not Acceptable Here", | |
| 95 491: "Request Pending", | |
| 96 493: "Undecipherable", | |
| 97 | |
| 98 500: "Internal Server Error", | |
| 99 501: "Not Implemented", | |
| 100 502: "Bad Gateway", # no donut | |
| 101 503: "Service Unavailable", | |
| 102 504: "Server Time-out", | |
| 103 505: "SIP Version not supported", | |
| 104 513: "Message Too Large", | |
| 105 | |
| 106 600: "Busy Everywhere", | |
| 107 603: "Decline", | |
| 108 604: "Does not exist anywhere", | |
| 109 606: "Not Acceptable", | |
| 110 } | |
| 111 | |
| 112 specialCases = { | |
| 113 'cseq': 'CSeq', | |
| 114 'call-id': 'Call-ID', | |
| 115 'www-authenticate': 'WWW-Authenticate', | |
| 116 } | |
| 117 | |
| 118 def dashCapitalize(s): | |
| 119 ''' Capitalize a string, making sure to treat - as a word seperator ''' | |
| 120 return '-'.join([ x.capitalize() for x in s.split('-')]) | |
| 121 | |
| 122 def unq(s): | |
| 123 if s[0] == s[-1] == '"': | |
| 124 return s[1:-1] | |
| 125 return s | |
| 126 | |
| 127 def DigestCalcHA1( | |
| 128 pszAlg, | |
| 129 pszUserName, | |
| 130 pszRealm, | |
| 131 pszPassword, | |
| 132 pszNonce, | |
| 133 pszCNonce, | |
| 134 ): | |
| 135 m = md5.md5() | |
| 136 m.update(pszUserName) | |
| 137 m.update(":") | |
| 138 m.update(pszRealm) | |
| 139 m.update(":") | |
| 140 m.update(pszPassword) | |
| 141 HA1 = m.digest() | |
| 142 if pszAlg == "md5-sess": | |
| 143 m = md5.md5() | |
| 144 m.update(HA1) | |
| 145 m.update(":") | |
| 146 m.update(pszNonce) | |
| 147 m.update(":") | |
| 148 m.update(pszCNonce) | |
| 149 HA1 = m.digest() | |
| 150 return HA1.encode('hex') | |
| 151 | |
| 152 def DigestCalcResponse( | |
| 153 HA1, | |
| 154 pszNonce, | |
| 155 pszNonceCount, | |
| 156 pszCNonce, | |
| 157 pszQop, | |
| 158 pszMethod, | |
| 159 pszDigestUri, | |
| 160 pszHEntity, | |
| 161 ): | |
| 162 m = md5.md5() | |
| 163 m.update(pszMethod) | |
| 164 m.update(":") | |
| 165 m.update(pszDigestUri) | |
| 166 if pszQop == "auth-int": | |
| 167 m.update(":") | |
| 168 m.update(pszHEntity) | |
| 169 HA2 = m.digest().encode('hex') | |
| 170 | |
| 171 m = md5.md5() | |
| 172 m.update(HA1) | |
| 173 m.update(":") | |
| 174 m.update(pszNonce) | |
| 175 m.update(":") | |
| 176 if pszNonceCount and pszCNonce: # pszQop: | |
| 177 m.update(pszNonceCount) | |
| 178 m.update(":") | |
| 179 m.update(pszCNonce) | |
| 180 m.update(":") | |
| 181 m.update(pszQop) | |
| 182 m.update(":") | |
| 183 m.update(HA2) | |
| 184 hash = m.digest().encode('hex') | |
| 185 return hash | |
| 186 | |
| 187 class Via: | |
| 188 """A SIP Via header.""" | |
| 189 | |
| 190 def __init__(self, host, port=PORT, transport="UDP", ttl=None, hidden=False, | |
| 191 received=None, rport=None, branch=None, maddr=None): | |
| 192 self.transport = transport | |
| 193 self.host = host | |
| 194 self.port = port | |
| 195 self.ttl = ttl | |
| 196 self.hidden = hidden | |
| 197 self.received = received | |
| 198 self.rport = rport | |
| 199 self.branch = branch | |
| 200 self.maddr = maddr | |
| 201 | |
| 202 def toString(self): | |
| 203 s = "SIP/2.0/%s %s:%s" % (self.transport, self.host, self.port) | |
| 204 if self.hidden: | |
| 205 s += ";hidden" | |
| 206 for n in "ttl", "branch", "maddr", "received", "rport": | |
| 207 value = getattr(self, n) | |
| 208 if value == True: | |
| 209 s += ";" + n | |
| 210 elif value != None: | |
| 211 s += ";%s=%s" % (n, value) | |
| 212 return s | |
| 213 | |
| 214 | |
| 215 def parseViaHeader(value): | |
| 216 """Parse a Via header, returning Via class instance.""" | |
| 217 parts = value.split(";") | |
| 218 sent, params = parts[0], parts[1:] | |
| 219 protocolinfo, by = sent.split(" ", 1) | |
| 220 by = by.strip() | |
| 221 result = {} | |
| 222 pname, pversion, transport = protocolinfo.split("/") | |
| 223 if pname != "SIP" or pversion != "2.0": | |
| 224 raise ValueError, "wrong protocol or version: %r" % value | |
| 225 result["transport"] = transport | |
| 226 if ":" in by: | |
| 227 host, port = by.split(":") | |
| 228 result["port"] = int(port) | |
| 229 result["host"] = host | |
| 230 else: | |
| 231 result["host"] = by | |
| 232 for p in params: | |
| 233 # it's the comment-striping dance! | |
| 234 p = p.strip().split(" ", 1) | |
| 235 if len(p) == 1: | |
| 236 p, comment = p[0], "" | |
| 237 else: | |
| 238 p, comment = p | |
| 239 if p == "hidden": | |
| 240 result["hidden"] = True | |
| 241 continue | |
| 242 parts = p.split("=", 1) | |
| 243 if len(parts) == 1: | |
| 244 name, value = parts[0], True | |
| 245 else: | |
| 246 name, value = parts | |
| 247 if name in ("rport", "ttl"): | |
| 248 value = int(value) | |
| 249 result[name] = value | |
| 250 return Via(**result) | |
| 251 | |
| 252 | |
| 253 class URL: | |
| 254 """A SIP URL.""" | |
| 255 | |
| 256 def __init__(self, host, username=None, password=None, port=None, | |
| 257 transport=None, usertype=None, method=None, | |
| 258 ttl=None, maddr=None, tag=None, other=None, headers=None): | |
| 259 self.username = username | |
| 260 self.host = host | |
| 261 self.password = password | |
| 262 self.port = port | |
| 263 self.transport = transport | |
| 264 self.usertype = usertype | |
| 265 self.method = method | |
| 266 self.tag = tag | |
| 267 self.ttl = ttl | |
| 268 self.maddr = maddr | |
| 269 if other == None: | |
| 270 self.other = [] | |
| 271 else: | |
| 272 self.other = other | |
| 273 if headers == None: | |
| 274 self.headers = {} | |
| 275 else: | |
| 276 self.headers = headers | |
| 277 | |
| 278 def toString(self): | |
| 279 l = []; w = l.append | |
| 280 w("sip:") | |
| 281 if self.username != None: | |
| 282 w(self.username) | |
| 283 if self.password != None: | |
| 284 w(":%s" % self.password) | |
| 285 w("@") | |
| 286 w(self.host) | |
| 287 if self.port != None: | |
| 288 w(":%d" % self.port) | |
| 289 if self.usertype != None: | |
| 290 w(";user=%s" % self.usertype) | |
| 291 for n in ("transport", "ttl", "maddr", "method", "tag"): | |
| 292 v = getattr(self, n) | |
| 293 if v != None: | |
| 294 w(";%s=%s" % (n, v)) | |
| 295 for v in self.other: | |
| 296 w(";%s" % v) | |
| 297 if self.headers: | |
| 298 w("?") | |
| 299 w("&".join([("%s=%s" % (specialCases.get(h) or dashCapitalize(h), v)
) for (h, v) in self.headers.items()])) | |
| 300 return "".join(l) | |
| 301 | |
| 302 def __str__(self): | |
| 303 return self.toString() | |
| 304 | |
| 305 def __repr__(self): | |
| 306 return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host
, self.port, self.transport) | |
| 307 | |
| 308 | |
| 309 def parseURL(url, host=None, port=None): | |
| 310 """Return string into URL object. | |
| 311 | |
| 312 URIs are of of form 'sip:user@example.com'. | |
| 313 """ | |
| 314 d = {} | |
| 315 if not url.startswith("sip:"): | |
| 316 raise ValueError("unsupported scheme: " + url[:4]) | |
| 317 parts = url[4:].split(";") | |
| 318 userdomain, params = parts[0], parts[1:] | |
| 319 udparts = userdomain.split("@", 1) | |
| 320 if len(udparts) == 2: | |
| 321 userpass, hostport = udparts | |
| 322 upparts = userpass.split(":", 1) | |
| 323 if len(upparts) == 1: | |
| 324 d["username"] = upparts[0] | |
| 325 else: | |
| 326 d["username"] = upparts[0] | |
| 327 d["password"] = upparts[1] | |
| 328 else: | |
| 329 hostport = udparts[0] | |
| 330 hpparts = hostport.split(":", 1) | |
| 331 if len(hpparts) == 1: | |
| 332 d["host"] = hpparts[0] | |
| 333 else: | |
| 334 d["host"] = hpparts[0] | |
| 335 d["port"] = int(hpparts[1]) | |
| 336 if host != None: | |
| 337 d["host"] = host | |
| 338 if port != None: | |
| 339 d["port"] = port | |
| 340 for p in params: | |
| 341 if p == params[-1] and "?" in p: | |
| 342 d["headers"] = h = {} | |
| 343 p, headers = p.split("?", 1) | |
| 344 for header in headers.split("&"): | |
| 345 k, v = header.split("=") | |
| 346 h[k] = v | |
| 347 nv = p.split("=", 1) | |
| 348 if len(nv) == 1: | |
| 349 d.setdefault("other", []).append(p) | |
| 350 continue | |
| 351 name, value = nv | |
| 352 if name == "user": | |
| 353 d["usertype"] = value | |
| 354 elif name in ("transport", "ttl", "maddr", "method", "tag"): | |
| 355 if name == "ttl": | |
| 356 value = int(value) | |
| 357 d[name] = value | |
| 358 else: | |
| 359 d.setdefault("other", []).append(p) | |
| 360 return URL(**d) | |
| 361 | |
| 362 | |
| 363 def cleanRequestURL(url): | |
| 364 """Clean a URL from a Request line.""" | |
| 365 url.transport = None | |
| 366 url.maddr = None | |
| 367 url.ttl = None | |
| 368 url.headers = {} | |
| 369 | |
| 370 | |
| 371 def parseAddress(address, host=None, port=None, clean=0): | |
| 372 """Return (name, uri, params) for From/To/Contact header. | |
| 373 | |
| 374 @param clean: remove unnecessary info, usually for From and To headers. | |
| 375 """ | |
| 376 address = address.strip() | |
| 377 # simple 'sip:foo' case | |
| 378 if address.startswith("sip:"): | |
| 379 return "", parseURL(address, host=host, port=port), {} | |
| 380 params = {} | |
| 381 name, url = address.split("<", 1) | |
| 382 name = name.strip() | |
| 383 if name.startswith('"'): | |
| 384 name = name[1:] | |
| 385 if name.endswith('"'): | |
| 386 name = name[:-1] | |
| 387 url, paramstring = url.split(">", 1) | |
| 388 url = parseURL(url, host=host, port=port) | |
| 389 paramstring = paramstring.strip() | |
| 390 if paramstring: | |
| 391 for l in paramstring.split(";"): | |
| 392 if not l: | |
| 393 continue | |
| 394 k, v = l.split("=") | |
| 395 params[k] = v | |
| 396 if clean: | |
| 397 # rfc 2543 6.21 | |
| 398 url.ttl = None | |
| 399 url.headers = {} | |
| 400 url.transport = None | |
| 401 url.maddr = None | |
| 402 return name, url, params | |
| 403 | |
| 404 | |
| 405 class SIPError(Exception): | |
| 406 def __init__(self, code, phrase=None): | |
| 407 if phrase is None: | |
| 408 phrase = statusCodes[code] | |
| 409 Exception.__init__(self, "SIP error (%d): %s" % (code, phrase)) | |
| 410 self.code = code | |
| 411 self.phrase = phrase | |
| 412 | |
| 413 | |
| 414 class RegistrationError(SIPError): | |
| 415 """Registration was not possible.""" | |
| 416 | |
| 417 | |
| 418 class Message: | |
| 419 """A SIP message.""" | |
| 420 | |
| 421 length = None | |
| 422 | |
| 423 def __init__(self): | |
| 424 self.headers = util.OrderedDict() # map name to list of values | |
| 425 self.body = "" | |
| 426 self.finished = 0 | |
| 427 | |
| 428 def addHeader(self, name, value): | |
| 429 name = name.lower() | |
| 430 name = longHeaders.get(name, name) | |
| 431 if name == "content-length": | |
| 432 self.length = int(value) | |
| 433 self.headers.setdefault(name,[]).append(value) | |
| 434 | |
| 435 def bodyDataReceived(self, data): | |
| 436 self.body += data | |
| 437 | |
| 438 def creationFinished(self): | |
| 439 if (self.length != None) and (self.length != len(self.body)): | |
| 440 raise ValueError, "wrong body length" | |
| 441 self.finished = 1 | |
| 442 | |
| 443 def toString(self): | |
| 444 s = "%s\r\n" % self._getHeaderLine() | |
| 445 for n, vs in self.headers.items(): | |
| 446 for v in vs: | |
| 447 s += "%s: %s\r\n" % (specialCases.get(n) or dashCapitalize(n), v
) | |
| 448 s += "\r\n" | |
| 449 s += self.body | |
| 450 return s | |
| 451 | |
| 452 def _getHeaderLine(self): | |
| 453 raise NotImplementedError | |
| 454 | |
| 455 | |
| 456 class Request(Message): | |
| 457 """A Request for a URI""" | |
| 458 | |
| 459 | |
| 460 def __init__(self, method, uri, version="SIP/2.0"): | |
| 461 Message.__init__(self) | |
| 462 self.method = method | |
| 463 if isinstance(uri, URL): | |
| 464 self.uri = uri | |
| 465 else: | |
| 466 self.uri = parseURL(uri) | |
| 467 cleanRequestURL(self.uri) | |
| 468 | |
| 469 def __repr__(self): | |
| 470 return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toStr
ing()) | |
| 471 | |
| 472 def _getHeaderLine(self): | |
| 473 return "%s %s SIP/2.0" % (self.method, self.uri.toString()) | |
| 474 | |
| 475 | |
| 476 class Response(Message): | |
| 477 """A Response to a URI Request""" | |
| 478 | |
| 479 def __init__(self, code, phrase=None, version="SIP/2.0"): | |
| 480 Message.__init__(self) | |
| 481 self.code = code | |
| 482 if phrase == None: | |
| 483 phrase = statusCodes[code] | |
| 484 self.phrase = phrase | |
| 485 | |
| 486 def __repr__(self): | |
| 487 return "<SIP Response %d:%s>" % (id(self), self.code) | |
| 488 | |
| 489 def _getHeaderLine(self): | |
| 490 return "SIP/2.0 %s %s" % (self.code, self.phrase) | |
| 491 | |
| 492 | |
| 493 class MessagesParser(basic.LineReceiver): | |
| 494 """A SIP messages parser. | |
| 495 | |
| 496 Expects dataReceived, dataDone repeatedly, | |
| 497 in that order. Shouldn't be connected to actual transport. | |
| 498 """ | |
| 499 | |
| 500 version = "SIP/2.0" | |
| 501 acceptResponses = 1 | |
| 502 acceptRequests = 1 | |
| 503 state = "firstline" # or "headers", "body" or "invalid" | |
| 504 | |
| 505 debug = 0 | |
| 506 | |
| 507 def __init__(self, messageReceivedCallback): | |
| 508 self.messageReceived = messageReceivedCallback | |
| 509 self.reset() | |
| 510 | |
| 511 def reset(self, remainingData=""): | |
| 512 self.state = "firstline" | |
| 513 self.length = None # body length | |
| 514 self.bodyReceived = 0 # how much of the body we received | |
| 515 self.message = None | |
| 516 self.setLineMode(remainingData) | |
| 517 | |
| 518 def invalidMessage(self): | |
| 519 self.state = "invalid" | |
| 520 self.setRawMode() | |
| 521 | |
| 522 def dataDone(self): | |
| 523 # clear out any buffered data that may be hanging around | |
| 524 self.clearLineBuffer() | |
| 525 if self.state == "firstline": | |
| 526 return | |
| 527 if self.state != "body": | |
| 528 self.reset() | |
| 529 return | |
| 530 if self.length == None: | |
| 531 # no content-length header, so end of data signals message done | |
| 532 self.messageDone() | |
| 533 elif self.length < self.bodyReceived: | |
| 534 # aborted in the middle | |
| 535 self.reset() | |
| 536 else: | |
| 537 # we have enough data and message wasn't finished? something is wron
g | |
| 538 raise RuntimeError, "this should never happen" | |
| 539 | |
| 540 def dataReceived(self, data): | |
| 541 try: | |
| 542 basic.LineReceiver.dataReceived(self, data) | |
| 543 except: | |
| 544 log.err() | |
| 545 self.invalidMessage() | |
| 546 | |
| 547 def handleFirstLine(self, line): | |
| 548 """Expected to create self.message.""" | |
| 549 raise NotImplementedError | |
| 550 | |
| 551 def lineLengthExceeded(self, line): | |
| 552 self.invalidMessage() | |
| 553 | |
| 554 def lineReceived(self, line): | |
| 555 if self.state == "firstline": | |
| 556 while line.startswith("\n") or line.startswith("\r"): | |
| 557 line = line[1:] | |
| 558 if not line: | |
| 559 return | |
| 560 try: | |
| 561 a, b, c = line.split(" ", 2) | |
| 562 except ValueError: | |
| 563 self.invalidMessage() | |
| 564 return | |
| 565 if a == "SIP/2.0" and self.acceptResponses: | |
| 566 # response | |
| 567 try: | |
| 568 code = int(b) | |
| 569 except ValueError: | |
| 570 self.invalidMessage() | |
| 571 return | |
| 572 self.message = Response(code, c) | |
| 573 elif c == "SIP/2.0" and self.acceptRequests: | |
| 574 self.message = Request(a, b) | |
| 575 else: | |
| 576 self.invalidMessage() | |
| 577 return | |
| 578 self.state = "headers" | |
| 579 return | |
| 580 else: | |
| 581 assert self.state == "headers" | |
| 582 if line: | |
| 583 # XXX support multi-line headers | |
| 584 try: | |
| 585 name, value = line.split(":", 1) | |
| 586 except ValueError: | |
| 587 self.invalidMessage() | |
| 588 return | |
| 589 self.message.addHeader(name, value.lstrip()) | |
| 590 if name.lower() == "content-length": | |
| 591 try: | |
| 592 self.length = int(value.lstrip()) | |
| 593 except ValueError: | |
| 594 self.invalidMessage() | |
| 595 return | |
| 596 else: | |
| 597 # CRLF, we now have message body until self.length bytes, | |
| 598 # or if no length was given, until there is no more data | |
| 599 # from the connection sending us data. | |
| 600 self.state = "body" | |
| 601 if self.length == 0: | |
| 602 self.messageDone() | |
| 603 return | |
| 604 self.setRawMode() | |
| 605 | |
| 606 def messageDone(self, remainingData=""): | |
| 607 assert self.state == "body" | |
| 608 self.message.creationFinished() | |
| 609 self.messageReceived(self.message) | |
| 610 self.reset(remainingData) | |
| 611 | |
| 612 def rawDataReceived(self, data): | |
| 613 assert self.state in ("body", "invalid") | |
| 614 if self.state == "invalid": | |
| 615 return | |
| 616 if self.length == None: | |
| 617 self.message.bodyDataReceived(data) | |
| 618 else: | |
| 619 dataLen = len(data) | |
| 620 expectedLen = self.length - self.bodyReceived | |
| 621 if dataLen > expectedLen: | |
| 622 self.message.bodyDataReceived(data[:expectedLen]) | |
| 623 self.messageDone(data[expectedLen:]) | |
| 624 return | |
| 625 else: | |
| 626 self.bodyReceived += dataLen | |
| 627 self.message.bodyDataReceived(data) | |
| 628 if self.bodyReceived == self.length: | |
| 629 self.messageDone() | |
| 630 | |
| 631 | |
| 632 class Base(protocol.DatagramProtocol): | |
| 633 """Base class for SIP clients and servers.""" | |
| 634 | |
| 635 PORT = PORT | |
| 636 debug = False | |
| 637 | |
| 638 def __init__(self): | |
| 639 self.messages = [] | |
| 640 self.parser = MessagesParser(self.addMessage) | |
| 641 | |
| 642 def addMessage(self, msg): | |
| 643 self.messages.append(msg) | |
| 644 | |
| 645 def datagramReceived(self, data, addr): | |
| 646 self.parser.dataReceived(data) | |
| 647 self.parser.dataDone() | |
| 648 for m in self.messages: | |
| 649 self._fixupNAT(m, addr) | |
| 650 if self.debug: | |
| 651 log.msg("Received %r from %r" % (m.toString(), addr)) | |
| 652 if isinstance(m, Request): | |
| 653 self.handle_request(m, addr) | |
| 654 else: | |
| 655 self.handle_response(m, addr) | |
| 656 self.messages[:] = [] | |
| 657 | |
| 658 def _fixupNAT(self, message, (srcHost, srcPort)): | |
| 659 # RFC 2543 6.40.2, | |
| 660 senderVia = parseViaHeader(message.headers["via"][0]) | |
| 661 if senderVia.host != srcHost: | |
| 662 senderVia.received = srcHost | |
| 663 if senderVia.port != srcPort: | |
| 664 senderVia.rport = srcPort | |
| 665 message.headers["via"][0] = senderVia.toString() | |
| 666 elif senderVia.rport == True: | |
| 667 senderVia.received = srcHost | |
| 668 senderVia.rport = srcPort | |
| 669 message.headers["via"][0] = senderVia.toString() | |
| 670 | |
| 671 def deliverResponse(self, responseMessage): | |
| 672 """Deliver response. | |
| 673 | |
| 674 Destination is based on topmost Via header.""" | |
| 675 destVia = parseViaHeader(responseMessage.headers["via"][0]) | |
| 676 # XXX we don't do multicast yet | |
| 677 host = destVia.received or destVia.host | |
| 678 port = destVia.rport or destVia.port or self.PORT | |
| 679 destAddr = URL(host=host, port=port) | |
| 680 self.sendMessage(destAddr, responseMessage) | |
| 681 | |
| 682 def responseFromRequest(self, code, request): | |
| 683 """Create a response to a request message.""" | |
| 684 response = Response(code) | |
| 685 for name in ("via", "to", "from", "call-id", "cseq"): | |
| 686 response.headers[name] = request.headers.get(name, [])[:] | |
| 687 | |
| 688 return response | |
| 689 | |
| 690 def sendMessage(self, destURL, message): | |
| 691 """Send a message. | |
| 692 | |
| 693 @param destURL: C{URL}. This should be a *physical* URL, not a logical o
ne. | |
| 694 @param message: The message to send. | |
| 695 """ | |
| 696 if destURL.transport not in ("udp", None): | |
| 697 raise RuntimeError, "only UDP currently supported" | |
| 698 if self.debug: | |
| 699 log.msg("Sending %r to %r" % (message.toString(), destURL)) | |
| 700 self.transport.write(message.toString(), (destURL.host, destURL.port or
self.PORT)) | |
| 701 | |
| 702 def handle_request(self, message, addr): | |
| 703 """Override to define behavior for requests received | |
| 704 | |
| 705 @type message: C{Message} | |
| 706 @type addr: C{tuple} | |
| 707 """ | |
| 708 raise NotImplementedError | |
| 709 | |
| 710 def handle_response(self, message, addr): | |
| 711 """Override to define behavior for responses received. | |
| 712 | |
| 713 @type message: C{Message} | |
| 714 @type addr: C{tuple} | |
| 715 """ | |
| 716 raise NotImplementedError | |
| 717 | |
| 718 | |
| 719 class IContact(Interface): | |
| 720 """A user of a registrar or proxy""" | |
| 721 | |
| 722 | |
| 723 class Registration: | |
| 724 def __init__(self, secondsToExpiry, contactURL): | |
| 725 self.secondsToExpiry = secondsToExpiry | |
| 726 self.contactURL = contactURL | |
| 727 | |
| 728 class IRegistry(Interface): | |
| 729 """Allows registration of logical->physical URL mapping.""" | |
| 730 | |
| 731 def registerAddress(domainURL, logicalURL, physicalURL): | |
| 732 """Register the physical address of a logical URL. | |
| 733 | |
| 734 @return: Deferred of C{Registration} or failure with RegistrationError. | |
| 735 """ | |
| 736 | |
| 737 def unregisterAddress(domainURL, logicalURL, physicalURL): | |
| 738 """Unregister the physical address of a logical URL. | |
| 739 | |
| 740 @return: Deferred of C{Registration} or failure with RegistrationError. | |
| 741 """ | |
| 742 | |
| 743 def getRegistrationInfo(logicalURL): | |
| 744 """Get registration info for logical URL. | |
| 745 | |
| 746 @return: Deferred of C{Registration} object or failure of LookupError. | |
| 747 """ | |
| 748 | |
| 749 | |
| 750 class ILocator(Interface): | |
| 751 """Allow looking up physical address for logical URL.""" | |
| 752 | |
| 753 def getAddress(logicalURL): | |
| 754 """Return physical URL of server for logical URL of user. | |
| 755 | |
| 756 @param logicalURL: a logical C{URL}. | |
| 757 @return: Deferred which becomes URL or fails with LookupError. | |
| 758 """ | |
| 759 | |
| 760 | |
| 761 class Proxy(Base): | |
| 762 """SIP proxy.""" | |
| 763 | |
| 764 PORT = PORT | |
| 765 | |
| 766 locator = None # object implementing ILocator | |
| 767 | |
| 768 def __init__(self, host=None, port=PORT): | |
| 769 """Create new instance. | |
| 770 | |
| 771 @param host: our hostname/IP as set in Via headers. | |
| 772 @param port: our port as set in Via headers. | |
| 773 """ | |
| 774 self.host = host or socket.getfqdn() | |
| 775 self.port = port | |
| 776 Base.__init__(self) | |
| 777 | |
| 778 def getVia(self): | |
| 779 """Return value of Via header for this proxy.""" | |
| 780 return Via(host=self.host, port=self.port) | |
| 781 | |
| 782 def handle_request(self, message, addr): | |
| 783 # send immediate 100/trying message before processing | |
| 784 #self.deliverResponse(self.responseFromRequest(100, message)) | |
| 785 f = getattr(self, "handle_%s_request" % message.method, None) | |
| 786 if f is None: | |
| 787 f = self.handle_request_default | |
| 788 try: | |
| 789 d = f(message, addr) | |
| 790 except SIPError, e: | |
| 791 self.deliverResponse(self.responseFromRequest(e.code, message)) | |
| 792 except: | |
| 793 log.err() | |
| 794 self.deliverResponse(self.responseFromRequest(500, message)) | |
| 795 else: | |
| 796 if d is not None: | |
| 797 d.addErrback(lambda e: | |
| 798 self.deliverResponse(self.responseFromRequest(e.code, messag
e)) | |
| 799 ) | |
| 800 | |
| 801 def handle_request_default(self, message, (srcHost, srcPort)): | |
| 802 """Default request handler. | |
| 803 | |
| 804 Default behaviour for OPTIONS and unknown methods for proxies | |
| 805 is to forward message on to the client. | |
| 806 | |
| 807 Since at the moment we are stateless proxy, thats basically | |
| 808 everything. | |
| 809 """ | |
| 810 def _mungContactHeader(uri, message): | |
| 811 message.headers['contact'][0] = uri.toString() | |
| 812 return self.sendMessage(uri, message) | |
| 813 | |
| 814 viaHeader = self.getVia() | |
| 815 if viaHeader.toString() in message.headers["via"]: | |
| 816 # must be a loop, so drop message | |
| 817 log.msg("Dropping looped message.") | |
| 818 return | |
| 819 | |
| 820 message.headers["via"].insert(0, viaHeader.toString()) | |
| 821 name, uri, tags = parseAddress(message.headers["to"][0], clean=1) | |
| 822 | |
| 823 # this is broken and needs refactoring to use cred | |
| 824 d = self.locator.getAddress(uri) | |
| 825 d.addCallback(self.sendMessage, message) | |
| 826 d.addErrback(self._cantForwardRequest, message) | |
| 827 | |
| 828 def _cantForwardRequest(self, error, message): | |
| 829 error.trap(LookupError) | |
| 830 del message.headers["via"][0] # this'll be us | |
| 831 self.deliverResponse(self.responseFromRequest(404, message)) | |
| 832 | |
| 833 def deliverResponse(self, responseMessage): | |
| 834 """Deliver response. | |
| 835 | |
| 836 Destination is based on topmost Via header.""" | |
| 837 destVia = parseViaHeader(responseMessage.headers["via"][0]) | |
| 838 # XXX we don't do multicast yet | |
| 839 host = destVia.received or destVia.host | |
| 840 port = destVia.rport or destVia.port or self.PORT | |
| 841 | |
| 842 destAddr = URL(host=host, port=port) | |
| 843 self.sendMessage(destAddr, responseMessage) | |
| 844 | |
| 845 def responseFromRequest(self, code, request): | |
| 846 """Create a response to a request message.""" | |
| 847 response = Response(code) | |
| 848 for name in ("via", "to", "from", "call-id", "cseq"): | |
| 849 response.headers[name] = request.headers.get(name, [])[:] | |
| 850 return response | |
| 851 | |
| 852 def handle_response(self, message, addr): | |
| 853 """Default response handler.""" | |
| 854 v = parseViaHeader(message.headers["via"][0]) | |
| 855 if (v.host, v.port) != (self.host, self.port): | |
| 856 # we got a message not intended for us? | |
| 857 # XXX note this check breaks if we have multiple external IPs | |
| 858 # yay for suck protocols | |
| 859 log.msg("Dropping incorrectly addressed message") | |
| 860 return | |
| 861 del message.headers["via"][0] | |
| 862 if not message.headers["via"]: | |
| 863 # this message is addressed to us | |
| 864 self.gotResponse(message, addr) | |
| 865 return | |
| 866 self.deliverResponse(message) | |
| 867 | |
| 868 def gotResponse(self, message, addr): | |
| 869 """Called with responses that are addressed at this server.""" | |
| 870 pass | |
| 871 | |
| 872 class IAuthorizer(Interface): | |
| 873 def getChallenge(peer): | |
| 874 """Generate a challenge the client may respond to. | |
| 875 | |
| 876 @type peer: C{tuple} | |
| 877 @param peer: The client's address | |
| 878 | |
| 879 @rtype: C{str} | |
| 880 @return: The challenge string | |
| 881 """ | |
| 882 | |
| 883 def decode(response): | |
| 884 """Create a credentials object from the given response. | |
| 885 | |
| 886 @type response: C{str} | |
| 887 """ | |
| 888 | |
| 889 class BasicAuthorizer: | |
| 890 """Authorizer for insecure Basic (base64-encoded plaintext) authentication. | |
| 891 | |
| 892 This form of authentication is broken and insecure. Do not use it. | |
| 893 """ | |
| 894 | |
| 895 implements(IAuthorizer) | |
| 896 | |
| 897 def getChallenge(self, peer): | |
| 898 return None | |
| 899 | |
| 900 def decode(self, response): | |
| 901 # At least one SIP client improperly pads its Base64 encoded messages | |
| 902 for i in range(3): | |
| 903 try: | |
| 904 creds = (response + ('=' * i)).decode('base64') | |
| 905 except: | |
| 906 pass | |
| 907 else: | |
| 908 break | |
| 909 else: | |
| 910 # Totally bogus | |
| 911 raise SIPError(400) | |
| 912 p = creds.split(':', 1) | |
| 913 if len(p) == 2: | |
| 914 return cred.credentials.UsernamePassword(*p) | |
| 915 raise SIPError(400) | |
| 916 | |
| 917 | |
| 918 class DigestedCredentials(cred.credentials.UsernameHashedPassword): | |
| 919 """Yet Another Simple Digest-MD5 authentication scheme""" | |
| 920 | |
| 921 def __init__(self, username, fields, challenges): | |
| 922 self.username = username | |
| 923 self.fields = fields | |
| 924 self.challenges = challenges | |
| 925 | |
| 926 def checkPassword(self, password): | |
| 927 method = 'REGISTER' | |
| 928 response = self.fields.get('response') | |
| 929 uri = self.fields.get('uri') | |
| 930 nonce = self.fields.get('nonce') | |
| 931 cnonce = self.fields.get('cnonce') | |
| 932 nc = self.fields.get('nc') | |
| 933 algo = self.fields.get('algorithm', 'MD5') | |
| 934 qop = self.fields.get('qop-options', 'auth') | |
| 935 opaque = self.fields.get('opaque') | |
| 936 | |
| 937 if opaque not in self.challenges: | |
| 938 return False | |
| 939 del self.challenges[opaque] | |
| 940 | |
| 941 user, domain = self.username.split('@', 1) | |
| 942 if uri is None: | |
| 943 uri = 'sip:' + domain | |
| 944 | |
| 945 expected = DigestCalcResponse( | |
| 946 DigestCalcHA1(algo, user, domain, password, nonce, cnonce), | |
| 947 nonce, nc, cnonce, qop, method, uri, None, | |
| 948 ) | |
| 949 | |
| 950 return expected == response | |
| 951 | |
| 952 class DigestAuthorizer: | |
| 953 CHALLENGE_LIFETIME = 15 | |
| 954 | |
| 955 implements(IAuthorizer) | |
| 956 | |
| 957 def __init__(self): | |
| 958 self.outstanding = {} | |
| 959 | |
| 960 def generateNonce(self): | |
| 961 c = tuple([random.randrange(sys.maxint) for _ in range(3)]) | |
| 962 c = '%d%d%d' % c | |
| 963 return c | |
| 964 | |
| 965 def generateOpaque(self): | |
| 966 return str(random.randrange(sys.maxint)) | |
| 967 | |
| 968 def getChallenge(self, peer): | |
| 969 c = self.generateNonce() | |
| 970 o = self.generateOpaque() | |
| 971 self.outstanding[o] = c | |
| 972 return ','.join(( | |
| 973 'nonce="%s"' % c, | |
| 974 'opaque="%s"' % o, | |
| 975 'qop-options="auth"', | |
| 976 'algorithm="MD5"', | |
| 977 )) | |
| 978 | |
| 979 def decode(self, response): | |
| 980 response = ' '.join(response.splitlines()) | |
| 981 parts = response.split(',') | |
| 982 auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1)
for p in parts]]) | |
| 983 try: | |
| 984 username = auth['username'] | |
| 985 except KeyError: | |
| 986 raise SIPError(401) | |
| 987 try: | |
| 988 return DigestedCredentials(username, auth, self.outstanding) | |
| 989 except: | |
| 990 raise SIPError(400) | |
| 991 | |
| 992 | |
| 993 class RegisterProxy(Proxy): | |
| 994 """A proxy that allows registration for a specific domain. | |
| 995 | |
| 996 Unregistered users won't be handled. | |
| 997 """ | |
| 998 | |
| 999 portal = None | |
| 1000 | |
| 1001 registry = None # should implement IRegistry | |
| 1002 | |
| 1003 authorizers = { | |
| 1004 'digest': DigestAuthorizer(), | |
| 1005 } | |
| 1006 | |
| 1007 def __init__(self, *args, **kw): | |
| 1008 Proxy.__init__(self, *args, **kw) | |
| 1009 self.liveChallenges = {} | |
| 1010 | |
| 1011 def handle_ACK_request(self, message, (host, port)): | |
| 1012 # XXX | |
| 1013 # ACKs are a client's way of indicating they got the last message | |
| 1014 # Responding to them is not a good idea. | |
| 1015 # However, we should keep track of terminal messages and re-transmit | |
| 1016 # if no ACK is received. | |
| 1017 pass | |
| 1018 | |
| 1019 def handle_REGISTER_request(self, message, (host, port)): | |
| 1020 """Handle a registration request. | |
| 1021 | |
| 1022 Currently registration is not proxied. | |
| 1023 """ | |
| 1024 if self.portal is None: | |
| 1025 # There is no portal. Let anyone in. | |
| 1026 self.register(message, host, port) | |
| 1027 else: | |
| 1028 # There is a portal. Check for credentials. | |
| 1029 if not message.headers.has_key("authorization"): | |
| 1030 return self.unauthorized(message, host, port) | |
| 1031 else: | |
| 1032 return self.login(message, host, port) | |
| 1033 | |
| 1034 def unauthorized(self, message, host, port): | |
| 1035 m = self.responseFromRequest(401, message) | |
| 1036 for (scheme, auth) in self.authorizers.iteritems(): | |
| 1037 chal = auth.getChallenge((host, port)) | |
| 1038 if chal is None: | |
| 1039 value = '%s realm="%s"' % (scheme.title(), self.host) | |
| 1040 else: | |
| 1041 value = '%s %s,realm="%s"' % (scheme.title(), chal, self.host) | |
| 1042 m.headers.setdefault('www-authenticate', []).append(value) | |
| 1043 self.deliverResponse(m) | |
| 1044 | |
| 1045 | |
| 1046 def login(self, message, host, port): | |
| 1047 parts = message.headers['authorization'][0].split(None, 1) | |
| 1048 a = self.authorizers.get(parts[0].lower()) | |
| 1049 if a: | |
| 1050 try: | |
| 1051 c = a.decode(parts[1]) | |
| 1052 except SIPError: | |
| 1053 raise | |
| 1054 except: | |
| 1055 log.err() | |
| 1056 self.deliverResponse(self.responseFromRequest(500, message)) | |
| 1057 else: | |
| 1058 c.username += '@' + self.host | |
| 1059 self.portal.login(c, None, IContact | |
| 1060 ).addCallback(self._cbLogin, message, host, port | |
| 1061 ).addErrback(self._ebLogin, message, host, port | |
| 1062 ).addErrback(log.err | |
| 1063 ) | |
| 1064 else: | |
| 1065 self.deliverResponse(self.responseFromRequest(501, message)) | |
| 1066 | |
| 1067 def _cbLogin(self, (i, a, l), message, host, port): | |
| 1068 # It's stateless, matey. What a joke. | |
| 1069 self.register(message, host, port) | |
| 1070 | |
| 1071 def _ebLogin(self, failure, message, host, port): | |
| 1072 failure.trap(cred.error.UnauthorizedLogin) | |
| 1073 self.unauthorized(message, host, port) | |
| 1074 | |
| 1075 def register(self, message, host, port): | |
| 1076 """Allow all users to register""" | |
| 1077 name, toURL, params = parseAddress(message.headers["to"][0], clean=1) | |
| 1078 contact = None | |
| 1079 if message.headers.has_key("contact"): | |
| 1080 contact = message.headers["contact"][0] | |
| 1081 | |
| 1082 if message.headers.get("expires", [None])[0] == "0": | |
| 1083 self.unregister(message, toURL, contact) | |
| 1084 else: | |
| 1085 # XXX Check expires on appropriate URL, and pass it to registry | |
| 1086 # instead of having registry hardcode it. | |
| 1087 if contact is not None: | |
| 1088 name, contactURL, params = parseAddress(contact, host=host, port
=port) | |
| 1089 d = self.registry.registerAddress(message.uri, toURL, contactURL
) | |
| 1090 else: | |
| 1091 d = self.registry.getRegistrationInfo(toURL) | |
| 1092 d.addCallbacks(self._cbRegister, self._ebRegister, | |
| 1093 callbackArgs=(message,), | |
| 1094 errbackArgs=(message,) | |
| 1095 ) | |
| 1096 | |
| 1097 def _cbRegister(self, registration, message): | |
| 1098 response = self.responseFromRequest(200, message) | |
| 1099 if registration.contactURL != None: | |
| 1100 response.addHeader("contact", registration.contactURL.toString()) | |
| 1101 response.addHeader("expires", "%d" % registration.secondsToExpiry) | |
| 1102 response.addHeader("content-length", "0") | |
| 1103 self.deliverResponse(response) | |
| 1104 | |
| 1105 def _ebRegister(self, error, message): | |
| 1106 error.trap(RegistrationError, LookupError) | |
| 1107 # XXX return error message, and alter tests to deal with | |
| 1108 # this, currently tests assume no message sent on failure | |
| 1109 | |
| 1110 def unregister(self, message, toURL, contact): | |
| 1111 try: | |
| 1112 expires = int(message.headers["expires"][0]) | |
| 1113 except ValueError: | |
| 1114 self.deliverResponse(self.responseFromRequest(400, message)) | |
| 1115 else: | |
| 1116 if expires == 0: | |
| 1117 if contact == "*": | |
| 1118 contactURL = "*" | |
| 1119 else: | |
| 1120 name, contactURL, params = parseAddress(contact) | |
| 1121 d = self.registry.unregisterAddress(message.uri, toURL, contactU
RL) | |
| 1122 d.addCallback(self._cbUnregister, message | |
| 1123 ).addErrback(self._ebUnregister, message | |
| 1124 ) | |
| 1125 | |
| 1126 def _cbUnregister(self, registration, message): | |
| 1127 msg = self.responseFromRequest(200, message) | |
| 1128 msg.headers.setdefault('contact', []).append(registration.contactURL.toS
tring()) | |
| 1129 msg.addHeader("expires", "0") | |
| 1130 self.deliverResponse(msg) | |
| 1131 | |
| 1132 def _ebUnregister(self, registration, message): | |
| 1133 pass | |
| 1134 | |
| 1135 | |
| 1136 class InMemoryRegistry: | |
| 1137 """A simplistic registry for a specific domain.""" | |
| 1138 | |
| 1139 implements(IRegistry, ILocator) | |
| 1140 | |
| 1141 def __init__(self, domain): | |
| 1142 self.domain = domain # the domain we handle registration for | |
| 1143 self.users = {} # map username to (IDelayedCall for expiry, address URI) | |
| 1144 | |
| 1145 def getAddress(self, userURI): | |
| 1146 if userURI.host != self.domain: | |
| 1147 return defer.fail(LookupError("unknown domain")) | |
| 1148 if self.users.has_key(userURI.username): | |
| 1149 dc, url = self.users[userURI.username] | |
| 1150 return defer.succeed(url) | |
| 1151 else: | |
| 1152 return defer.fail(LookupError("no such user")) | |
| 1153 | |
| 1154 def getRegistrationInfo(self, userURI): | |
| 1155 if userURI.host != self.domain: | |
| 1156 return defer.fail(LookupError("unknown domain")) | |
| 1157 if self.users.has_key(userURI.username): | |
| 1158 dc, url = self.users[userURI.username] | |
| 1159 return defer.succeed(Registration(int(dc.getTime() - time.time()), u
rl)) | |
| 1160 else: | |
| 1161 return defer.fail(LookupError("no such user")) | |
| 1162 | |
| 1163 def _expireRegistration(self, username): | |
| 1164 try: | |
| 1165 dc, url = self.users[username] | |
| 1166 except KeyError: | |
| 1167 return defer.fail(LookupError("no such user")) | |
| 1168 else: | |
| 1169 dc.cancel() | |
| 1170 del self.users[username] | |
| 1171 return defer.succeed(Registration(0, url)) | |
| 1172 | |
| 1173 def registerAddress(self, domainURL, logicalURL, physicalURL): | |
| 1174 if domainURL.host != self.domain: | |
| 1175 log.msg("Registration for domain we don't handle.") | |
| 1176 return defer.fail(RegistrationError(404)) | |
| 1177 if logicalURL.host != self.domain: | |
| 1178 log.msg("Registration for domain we don't handle.") | |
| 1179 return defer.fail(RegistrationError(404)) | |
| 1180 if self.users.has_key(logicalURL.username): | |
| 1181 dc, old = self.users[logicalURL.username] | |
| 1182 dc.reset(3600) | |
| 1183 else: | |
| 1184 dc = reactor.callLater(3600, self._expireRegistration, logicalURL.us
ername) | |
| 1185 log.msg("Registered %s at %s" % (logicalURL.toString(), physicalURL.toSt
ring())) | |
| 1186 self.users[logicalURL.username] = (dc, physicalURL) | |
| 1187 return defer.succeed(Registration(int(dc.getTime() - time.time()), physi
calURL)) | |
| 1188 | |
| 1189 def unregisterAddress(self, domainURL, logicalURL, physicalURL): | |
| 1190 return self._expireRegistration(logicalURL.username) | |
| OLD | NEW |