~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/protocols/sip.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_sip -*-
 
2
 
 
3
# Copyright (c) 2001-2009 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)