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