Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(339)

Side by Side Diff: third_party/twisted_8_1/twisted/protocols/sip.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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)
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/protocols/shoutcast.py ('k') | third_party/twisted_8_1/twisted/protocols/smtp.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698