Merge lp:~cyli/divmod.org/remove-vertex into lp:divmod.org

Proposed by cyli
Status: Merged
Approved by: Allen Short
Approved revision: 2702
Merged at revision: 2702
Proposed branch: lp:~cyli/divmod.org/remove-vertex
Merge into: lp:divmod.org
Diff against target: 9122 lines (+1/-8893)
41 files modified
Divmod.pth (+1/-2)
Vertex/DEPS.txt (+0/-5)
Vertex/LICENSE (+0/-20)
Vertex/MANIFEST.in (+0/-4)
Vertex/NAME.txt (+0/-10)
Vertex/NEWS.txt (+0/-20)
Vertex/README.txt (+0/-12)
Vertex/bin/gvertex (+0/-7)
Vertex/bin/vertex (+0/-7)
Vertex/doc/notes (+0/-61)
Vertex/doc/q2q-standalone.tac (+0/-5)
Vertex/prime/plugins/vertex_client.py (+0/-4)
Vertex/setup.py (+0/-31)
Vertex/vertex/__init__.py (+0/-3)
Vertex/vertex/_version.py (+0/-3)
Vertex/vertex/bits.py (+0/-142)
Vertex/vertex/conncache.py (+0/-160)
Vertex/vertex/depserv.py (+0/-197)
Vertex/vertex/endpoint.py (+0/-56)
Vertex/vertex/gtk2hack.glade (+0/-635)
Vertex/vertex/gtk2hack.py (+0/-270)
Vertex/vertex/ivertex.py (+0/-108)
Vertex/vertex/ptcp.py (+0/-1050)
Vertex/vertex/q2q.py (+0/-2763)
Vertex/vertex/q2qadmin.py (+0/-21)
Vertex/vertex/q2qclient.py (+0/-453)
Vertex/vertex/q2qstandalone.py (+0/-108)
Vertex/vertex/sigma.py (+0/-760)
Vertex/vertex/statemachine.py (+0/-56)
Vertex/vertex/subproducer.py (+0/-125)
Vertex/vertex/tcpdfa.py (+0/-96)
Vertex/vertex/test/__init__.py (+0/-3)
Vertex/vertex/test/mock_data.py (+0/-2)
Vertex/vertex/test/test_bits.py (+0/-68)
Vertex/vertex/test/test_client.py (+0/-30)
Vertex/vertex/test/test_dependencyservice.py (+0/-69)
Vertex/vertex/test/test_ptcp.py (+0/-365)
Vertex/vertex/test/test_q2q.py (+0/-712)
Vertex/vertex/test/test_sigma.py (+0/-210)
Vertex/vertex/test/test_statemachine.py (+0/-67)
Vertex/vertex/test/test_subproducer.py (+0/-173)
To merge this branch: bzr merge lp:~cyli/divmod.org/remove-vertex
Reviewer Review Type Date Requested Status
Allen Short Approve
Review via email: mp+171717@code.launchpad.net

Description of the change

Proposing to remove Vertex from the divmod repos. I found only one branch with a merge proposal for it:

https://code.launchpad.net/~alfred-54/divmod.org/divmod.org/+merge/158833

I've ported trunk as well as alfred-54's branch to https://github.com/cyli/vertex, which I will then transfer to the twistedmatrix organization after/if this branch is merged into divmod.org repo.

To post a comment you must log in.
Revision history for this message
Allen Short (washort) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Divmod.pth'
--- Divmod.pth 2013-01-02 10:08:46 +0000
+++ Divmod.pth 2013-06-27 06:07:28 +0000
@@ -1,4 +1,4 @@
1# -*- test-case-name: axiom,combinator,epsilon,xmantissa,nevow,formless,xquotient,reverend,sine,vertex,hyperbola,imaginary,examplegame -*-1# -*- test-case-name: axiom,combinator,epsilon,xmantissa,nevow,formless,xquotient,reverend,sine,hyperbola,imaginary,examplegame -*-
2Axiom2Axiom
3Combinator3Combinator
4Epsilon4Epsilon
@@ -7,7 +7,6 @@
7Quotient7Quotient
8Reverend8Reverend
9Sine9Sine
10Vertex
11Hyperbola10Hyperbola
12Imaginary11Imaginary
13Imaginary/ExampleGame12Imaginary/ExampleGame
1413
=== removed directory 'Vertex'
=== removed file 'Vertex/DEPS.txt'
--- Vertex/DEPS.txt 2006-06-14 11:54:41 +0000
+++ Vertex/DEPS.txt 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1Python 2.4
2Twisted 2.4.0
3PyOpenSSL 0.6
4OpenSSL 0.9.7
5Epsilon 0.5.0
60
=== removed file 'Vertex/LICENSE'
--- Vertex/LICENSE 2005-12-10 22:31:51 +0000
+++ Vertex/LICENSE 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
1Copyright (c) 2005 Divmod Inc.
2
3Permission is hereby granted, free of charge, to any person obtaining
4a copy of this software and associated documentation files (the
5"Software"), to deal in the Software without restriction, including
6without limitation the rights to use, copy, modify, merge, publish,
7distribute, sublicense, and/or sell copies of the Software, and to
8permit persons to whom the Software is furnished to do so, subject to
9the following conditions:
10
11The above copyright notice and this permission notice shall be
12included in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21\ No newline at end of file0\ No newline at end of file
221
=== removed file 'Vertex/MANIFEST.in'
--- Vertex/MANIFEST.in 2006-06-14 11:54:41 +0000
+++ Vertex/MANIFEST.in 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1include LICENSE
2include NAME.txt
3include DEPS.txt
4include doc/q2q-standalone.tac
50
=== removed file 'Vertex/NAME.txt'
--- Vertex/NAME.txt 2005-08-27 23:09:07 +0000
+++ Vertex/NAME.txt 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1
2See: http://mathworld.wolfram.com/Vertex.html
3
4A vertex in mathematics is a location where two or more lines or edges meet.
5
6Divmod Vertex is an implementation of and interface to the Q2Q protocol. It is
7named for a vertext because it provides a way for peers on the internet to
8establish connections with each other, e.g. to make their connection lines
9meet, regardless of intermediary interference such as network address
10translators and lack of naming information.
110
=== removed file 'Vertex/NEWS.txt'
--- Vertex/NEWS.txt 2009-11-30 01:08:55 +0000
+++ Vertex/NEWS.txt 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
10.3.0 (2009-11-25):
2 - Remove use of deprecated Twisted APIs from the test suite and improve
3 some error handling as a necessary consequence.
4 - Use twisted.internet.ssl instead of epsilon.sslverify.
5 - Remove an implementation of deferLater.
6
70.2.0 (2006-06-12):
8 - Moved JUICE implementation into Epsilon.
9 - Removed dependency on Nevow's formless.
10 - Clarify licensing terms.
11 - Fix bugs on 64-bit platforms.
12 - removed buggy legacy non-TLS options which would break negotiation with
13 OpenSSL 0.9.8a.
14 - Deprecated twisted test APIs removed.
15 - First phase of integration with twisted.cred; vertex endpoints can now be
16 authenticated against a Twisted UsernamePassword cred authenticator.
17
180.1.0 (2005-10-10):
19
20 - Initial release.
210
=== removed file 'Vertex/README.txt'
--- Vertex/README.txt 2006-06-14 11:54:41 +0000
+++ Vertex/README.txt 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1
2Divmod Vertex
3=============
4
5Divmod Vertex is the first implementation of the Q2Q protocol, which is a
6peer-to-peer communication protocol for establishing stream-based communication
7between named endpoints.
8
9It is also a P2P application client and server platform in the early stages of
10development. It is currently quite usable for knocking holes in firewalls, but
11requires some polish to really be usable.
12
130
=== removed directory 'Vertex/bin'
=== removed file 'Vertex/bin/gvertex'
--- Vertex/bin/gvertex 2006-03-08 04:10:37 +0000
+++ Vertex/bin/gvertex 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1#!/usr/bin/python
2# Copyright 2005 Divmod, Inc. See LICENSE file for details
3
4from vertex.gtk2hack import main
5
6if __name__ == '__main__':
7 main()
80
=== removed file 'Vertex/bin/vertex'
--- Vertex/bin/vertex 2005-08-10 22:20:03 +0000
+++ Vertex/bin/vertex 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1#!/usr/bin/python
2# Copyright 2005 Divmod, Inc. See LICENSE file for details
3
4from vertex.q2qclient import Q2QClientProgram
5
6if __name__ == '__main__':
7 Q2QClientProgram().parseOptions()
80
=== removed directory 'Vertex/doc'
=== removed file 'Vertex/doc/notes'
--- Vertex/doc/notes 2005-08-05 03:13:02 +0000
+++ Vertex/doc/notes 1970-01-01 00:00:00 +0000
@@ -1,61 +0,0 @@
1
2Gin
3===
4
5TCP-alike over UDP
6
7Packet Format
8=============
9
10Connection ID - 32 bits
11Sequence Number - 32 bits
12Ack number - 32 bits
13Window - 32 bits
14Flags (SYN, ACK, FIN, IGN) - 8 bits
15Checksum - 32 bits
16Data length in bytes - 16 bits
17Data - See previous
18
19SEMANTICS
20=========
21Different types of packets:
22
23just SYN:
24SYN+ACK:
25just ACK: normal tcp packet meanings
26
27SYN+NAT: Request for information about the sender's public address
28
29ACK+NAT: Response with information about the recipient's public address. The
30data is the IP address and port number to which the packet is being sent,
31formatted as a dotted-quad formatted IP address followed by a colon followed by
32the base-10 string representation of the port number..
33
34STB: Size Too Big - a packet with a dlen greater than the length of its data
35was received.
36
37Other Stuff:
38
39Connection IDs uniquely identify a stream for a protocol. All bytes
40associated with a particular connectionID will be delivered to the
41same protocol instance.
42
43Sequence Numbers indicate the senders notion of how far into its
44outgoing stream this packet is. Sequence numbers start from a
45pseudo-random value within the allowed range and are incremented by
46the number of bytes in each packet transmitted (re-transmits
47discounted). This indicates to the peer where the bytes in each
48packet lie in the stream, allowing ordered delivery.
49
50Ack numbers indicate the senders notion of how far into its incoming
51stream all data has been received. This value is always what the
52sender expects the receiver to use as its next sequence number.
53
54Window indicates the number of bytes in advance of the senders ack
55number the receiver may proceed in sending. This receiver's sequence
56numbers must never be greater than the sender's last ack number plus
57their window number.
58
59Checksum is a CRC 32 of the data (and only the data - wait maybe this
60should be the header less the checksum, too, in case things get
61corrupted there)
620
=== removed file 'Vertex/doc/q2q-standalone.tac'
--- Vertex/doc/q2q-standalone.tac 2005-08-10 22:20:03 +0000
+++ Vertex/doc/q2q-standalone.tac 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2from vertex.q2qstandalone import defaultConfig
3
4application = defaultConfig()
5
60
=== removed directory 'Vertex/prime'
=== removed directory 'Vertex/prime/plugins'
=== removed file 'Vertex/prime/plugins/vertex_client.py'
--- Vertex/prime/plugins/vertex_client.py 2006-06-01 04:57:02 +0000
+++ Vertex/prime/plugins/vertex_client.py 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1
2from vertex.gtk2hack import PlugEntry
3
4pe = PlugEntry()
50
=== removed file 'Vertex/setup.py'
--- Vertex/setup.py 2009-11-30 01:08:55 +0000
+++ Vertex/setup.py 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1from epsilon import setuphelper
2
3from vertex import version
4
5setuphelper.autosetup(
6 name="Vertex",
7 version=version.short(),
8 maintainer="Divmod, Inc.",
9 maintainer_email="support@divmod.org",
10 url="http://divmod.org/trac/wiki/DivmodVertex",
11 license="MIT",
12 platforms=["any"],
13 description=
14 """
15 Divmod Vertex is the first implementation of the Q2Q protocol, which
16 is a peer-to-peer communication protocol for establishing
17 stream-based communication between named endpoints.
18 """,
19 classifiers=[
20 "Development Status :: 2 - Pre-Alpha",
21 "Framework :: Twisted",
22 "Intended Audience :: Developers",
23 "License :: OSI Approved :: MIT License",
24 "Programming Language :: Python",
25 "Topic :: Communications",
26 "Topic :: Internet",
27 "Topic :: Internet :: File Transfer Protocol (FTP)",
28 "Topic :: Internet :: Name Service (DNS)",
29 "Topic :: Software Development :: Libraries :: Python Modules",
30 ],
31 )
320
=== removed directory 'Vertex/vertex'
=== removed file 'Vertex/vertex/__init__.py'
--- Vertex/vertex/__init__.py 2008-08-11 11:19:59 +0000
+++ Vertex/vertex/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1# -*- test-case-name: vertex.test -*-
2
3from vertex._version import version
40
=== removed file 'Vertex/vertex/_version.py'
--- Vertex/vertex/_version.py 2009-11-30 01:08:55 +0000
+++ Vertex/vertex/_version.py 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1# This is an auto-generated file. Use admin/change-versions to update.
2from twisted.python import versions
3version = versions.Version(__name__[:__name__.rfind('.')], 0, 3, 0)
40
=== removed file 'Vertex/vertex/bits.py'
--- Vertex/vertex/bits.py 2005-08-05 06:02:56 +0000
+++ Vertex/vertex/bits.py 1970-01-01 00:00:00 +0000
@@ -1,142 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2# -*- test-case-name: vertex.test.test_bits -*-
3""" The purpose of this module is to provide the class BitArray, a compact
4overlay onto an array of bytes which is instead bit-addressable. It also
5includes several bitwise operators.
6
7It does not include all array operations yet, most notably those related to
8slicing, since it is written primarily for use by the swarming implementation
9and swarming only requires fixed-size bit masks.
10
11"""
12
13__metaclass__ = type
14
15import array
16import operator
17import math
18
19BITS_PER_BYTE = 8
20
21def operate(operation):
22 # XXX TODO: optimize this and countbits later
23 def __x__(self, other):
24 if len(self) < len(other):
25 return operation(other, self)
26 new = BitArray(size=len(self))
27 for offt, (mybit, hisbit) in enumerate(zip(self, other)):
28 result = new[offt] = operation(mybit, hisbit)
29
30 for j in range(offt+1, len(self)):
31 new[j] = operation(self[j], 0)
32 return new
33 return __x__
34
35
36class BitArray:
37 """
38 A large mutable array of bits.
39 """
40
41 def __init__(self, bytes=None, size=None, default=0):
42 if bytes is None and size is None:
43 size = 0
44 if bytes is None:
45 bytes = array.array("B")
46 bytesize = int(math.ceil(float(size) / BITS_PER_BYTE))
47 if default:
48 padbyte = 255
49 else:
50 padbyte = 0
51 bytes.fromlist([padbyte] * bytesize)
52 self.bytes = bytes
53 if size is None:
54 size = len(self.bytes) * self.bytes.itemsize * BITS_PER_BYTE
55 self.size = size
56
57 # initialize 'on' and 'off' lists to optimize various things
58 self.on = []
59 self.off = []
60 blists = self.blists = self.off, self.on
61
62 for index, bit in enumerate(self):
63 blists[bit].append(index)
64
65 def append(self, bit):
66 offt = self.size
67 self.size += 1
68 if (len(self.bytes) * self.bytes.itemsize * BITS_PER_BYTE) < self.size:
69 self.bytes.append(0)
70 self[offt] = bit
71
72 def any(self, req=1):
73 return bool(self.blists[req])
74
75 def percent(self):
76 """
77 debugging method; returns a string indicating percentage completion
78 """
79 if not len(self):
80 return 'Inf%'
81 return '%0.2f%%'% ((float(self.countbits()) / len(self)) * 100,)
82
83 def __getitem__(self, bitcount):
84 if bitcount < 0:
85 bitcount += self.size
86 if bitcount >= self.size:
87 raise IndexError("%r >= %r" % (bitcount, self.size))
88 div, mod = divmod(bitcount, self.bytes.itemsize * BITS_PER_BYTE)
89 byte = self.bytes[div]
90 return (byte >> mod) & 1
91
92 def __setitem__(self, bitcount, bit):
93 if bitcount < 0:
94 bitcount += self.size
95 if bitcount >= self.size:
96 raise IndexError("bitcount too big")
97 div, mod = divmod(bitcount, self.bytes.itemsize * BITS_PER_BYTE)
98 if bit:
99 self.bytes[div] |= 1 << mod
100 else:
101 self.bytes[div] &= ~(1 << mod)
102
103 # change updating
104 notbitlist = self.blists[not bit]
105 try:
106 notbitlist.remove(bitcount)
107 except ValueError:
108 pass
109 bitlist = self.blists[bit]
110 if bitcount not in bitlist:
111 bitlist.append(bitcount)
112
113 def __len__(self):
114 return self.size
115
116 def __repr__(self):
117 l = []
118 l.append('[')
119 for b in self:
120 if b:
121 c = 'X'
122 else:
123 c = ' '
124 l.append(c)
125 l.append(']')
126 return ''.join(l)
127
128 def countbits(self, on=True):
129 return len(self.blists[on])
130
131 def positions(self, bit):
132 """
133 An iterator of all positions that a bit holds in this BitArray.
134
135 @param bit: 1 or 0
136 """
137 return self.blists[bit][:]
138
139 __xor__ = operate(operator.xor)
140 __and__ = operate(operator.and_)
141 __or__ = operate(operator.or_)
142
1430
=== removed file 'Vertex/vertex/conncache.py'
--- Vertex/vertex/conncache.py 2009-07-06 11:40:18 +0000
+++ Vertex/vertex/conncache.py 1970-01-01 00:00:00 +0000
@@ -1,160 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2# -*- test-case-name: vertex.test.test_q2q.TCPConnection.testSendingFiles -*-
3
4"""
5Connect between two endpoints using a message-based protocol to exchange
6messages lazily in response to UI events, caching the protocol as necessary.
7Using connection-oriented protocols, you will most likely not want to use this
8class - you might end up retrieving a cached connection in the middle of a
9chunk of data being sent. For the purposes of this distinction, a
10'message-oriented' protocol is one which has an API which either::
11
12 a) writes only whole messages to its transport so there is never an
13 opportunity to insert data into the middle of a message, or
14
15 b) provides an API on the Protocol instance for queuing whole messages such
16 that if partial messages are sent, calling the API multiple times will
17 queue them internally so that clients do not need to care whether the
18 connection is made or not.
19
20It is worth noting that all Juice-derived protocols meet constraint (b).
21"""
22
23from zope.interface import implements
24
25from twisted.internet.defer import maybeDeferred, DeferredList, Deferred
26from twisted.internet.main import CONNECTION_LOST
27from twisted.internet import interfaces
28from twisted.internet.protocol import ClientFactory
29
30
31class ConnectionCache:
32 def __init__(self):
33 """
34 """
35 # map (fromAddress, toAddress, protoName): protocol instance
36 self.cachedConnections = {}
37 # map (fromAddress, toAddress, protoName): list of Deferreds
38 self.inProgress = {}
39
40 def connectCached(self, endpoint, protocolFactory,
41 extraWork=lambda x: x,
42 extraHash=None):
43 """See module docstring
44 """
45 key = endpoint, extraHash
46 D = Deferred()
47 if key in self.cachedConnections:
48 D.callback(self.cachedConnections[key])
49 elif key in self.inProgress:
50 self.inProgress[key].append(D)
51 else:
52 self.inProgress[key] = [D]
53 endpoint.connect(
54 _CachingClientFactory(
55 self, key, protocolFactory,
56 extraWork))
57 return D
58
59 def cacheUnrequested(self, endpoint, extraHash, protocol):
60 self.connectionMadeForKey((endpoint, extraHash), protocol)
61
62 def connectionMadeForKey(self, key, protocol):
63 deferreds = self.inProgress.pop(key, [])
64 self.cachedConnections[key] = protocol
65 for d in deferreds:
66 d.callback(protocol)
67
68 def connectionLostForKey(self, key):
69 if key in self.cachedConnections:
70 del self.cachedConnections[key]
71
72 def connectionFailedForKey(self, key, reason):
73 deferreds = self.inProgress.pop(key)
74 for d in deferreds:
75 d.errback(reason)
76
77 def shutdown(self):
78 return DeferredList(
79 [maybeDeferred(p.transport.loseConnection)
80 for p in self.cachedConnections.values()])
81
82
83class _CachingClientFactory(ClientFactory):
84 debug = False
85
86 def __init__(self, cache, key, subFactory, extraWork):
87 """
88 @param cache: a Q2QService
89
90 @param key: a 2-tuple of (endpoint, extra) that represents what
91 connections coming from this factory are for.
92
93 @param subFactory: a ClientFactory which I forward methods to.
94
95 @param extraWork: extraWork(proto) -> Deferred which fires when the
96 connection has been prepared sufficiently to be used by subsequent
97 connections and can be counted as a success.
98 """
99
100 self.cache = cache
101 self.key = key
102 self.subFactory = subFactory
103 self.finishedExtraWork = False
104 self.extraWork = extraWork
105
106 lostAsFailReason = CONNECTION_LOST
107
108 def clientConnectionMade(self, protocol):
109 def success(reason):
110 self.cache.connectionMadeForKey(self.key, protocol)
111 self.finishedExtraWork = True
112 return protocol
113
114 def failed(reason):
115 self.lostAsFailReason = reason
116 protocol.transport.loseConnection()
117 return reason
118 maybeDeferred(self.extraWork, protocol).addCallbacks(
119 success, failed)
120
121 def clientConnectionLost(self, connector, reason):
122 if self.finishedExtraWork:
123 self.cache.connectionLostForKey(self.key)
124 else:
125 self.cache.connectionFailedForKey(self.key,
126 self.lostAsFailReason)
127 self.subFactory.clientConnectionLost(connector, reason)
128
129 def clientConnectionFailed(self, connector, reason):
130 self.cache.connectionFailedForKey(self.key, reason)
131 self.subFactory.clientConnectionFailed(connector, reason)
132
133 def buildProtocol(self, addr):
134 return _CachingTransportShim(self, self.subFactory.buildProtocol(addr))
135
136
137class _CachingTransportShim:
138 disconnecting = property(lambda self: self.transport.disconnecting)
139
140 implements(interfaces.IProtocol)
141
142 def __init__(self, factory, protocol):
143 self.factory = factory
144 self.protocol = protocol
145
146 # IProtocol
147 self.dataReceived = protocol.dataReceived
148 self.connectionLost = protocol.connectionLost
149
150
151 def makeConnection(self, transport):
152 self.transport = transport
153 self.protocol.makeConnection(transport)
154 self.factory.clientConnectionMade(self.protocol)
155
156
157 def __repr__(self):
158 return 'Q2Q-Cached<%r, %r>' % (self.transport,
159 self.protocol)
160
1610
=== removed file 'Vertex/vertex/depserv.py'
--- Vertex/vertex/depserv.py 2009-07-06 11:40:18 +0000
+++ Vertex/vertex/depserv.py 1970-01-01 00:00:00 +0000
@@ -1,197 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2
3"""
4This module is no longer supported for use outside Vertex.
5"""
6
7from twisted.python import log
8from sets import Set
9from twisted.persisted import sob
10from twisted.application import service, internet
11
12from zope.interface import implements
13
14class Conf(dict):
15 """A class to help in construction the configuration for delpoy().
16
17 Typical usage::
18
19 from vertex.depserv import Conf
20 conf = Conf()
21 s = conf.section
22 s('pop',
23 port = 110,
24 sslPort = 995)
25 ...
26 """
27 def section(self, name, **kw):
28 self.setdefault(name, {}).update(kw)
29
30
31class NotPersistable:
32 implements(sob.IPersistable)
33 def __init__(self, original):
34 self.original = original
35
36 def setStyle(self, style):
37 self.style = style
38
39 def save(self, tag=None, filename=None, passphrase=None):
40 pass
41
42
43class StartupError(Exception):
44 pass
45
46
47class DependencyService(service.MultiService):
48 """A MultiService that can start multiple services with interdependencies.
49
50 Each keyword parameter is a dict which serves as the options for that
51 service.
52
53 Each service defines a method setup_SERVICE, which is called with the
54 matching parameters (the service name must be all caps). If there is no key
55 for SERVICE in the class parameters, the setup method is not called. The
56 return value is ignored, and DependencyService makes no assumptions about
57 any side effects.
58
59 Each service may also optionally define depends_SERVICE which is called
60 before the setup method with the same parameters as the setup method. This
61 method returns a list of names of services on which SERVICE depends.
62 DependencyService will then initialize the service is the correct order. If
63 circular dependencies result, or a service depends on another service which
64 does not exist or is not configured to run, StartupError is raised.
65
66 The class can define required services by setting 'requiredServices' to a
67 list of service names. These services will be initialized first in the
68 order they appear in the list, ignoring all dependency information. If
69 there are no parameters for a required service (consequently, the setup
70 method would not normally be called), StartupError is raised.
71 """
72
73
74 requiredServices = []
75
76
77 def __init__(self, **kw):
78 service.MultiService.__init__(self)
79
80 # this makes it possible for one service to change the configuration of
81 # another. Avoid if possible, there if you need it. Be sure to properly
82 # set the dependencies.
83 self.config = kw
84 self.servers = []
85
86 services = kw.keys()
87 initedServices = Set()
88 uninitedServices = Set(services)
89
90 # build dependencies
91 dependencies = {}
92 for serv in services:
93 try:
94 dependMethod = self._getDependsMethod(serv)
95 except AttributeError:
96 continue
97 dependencies[serv] = dependMethod(**kw[serv])
98
99 def initializeService(svc):
100 self._getServiceMethod(svc)(**kw[svc])
101 initedServices.add(svc)
102 uninitedServices.remove(svc)
103
104 for svc in self.requiredServices:
105 if dependencies.get(svc):
106 raise StartupError(
107 '%r is a required service but has unsatisfied '
108 'dependency on %r' % (svc, dependencies[svc]))
109 initializeService(svc)
110
111 while uninitedServices:
112 # iterate over the uninitialized services, adding those with no
113 # outstanding dependencies to initThisRound.
114 initThisRound = []
115 for serv in uninitedServices:
116 for dep in dependencies.get(serv, []):
117 if dep not in initedServices:
118 if dep not in uninitedServices:
119 raise StartupError(
120 'service %r depends on service %r, which is not '
121 'configured or does not exist.' % (serv, dep))
122 break
123 else:
124 initThisRound.append(serv)
125 if not initThisRound:
126 raise StartupError(
127 'Can not initialize all services. Circular dependencies '
128 'between setup methods?')
129 for svc in initThisRound:
130 initializeService(svc)
131
132
133 def _getServiceMethod(self, service):
134 return getattr(self, 'setup_%s' % (service.upper(),))
135
136
137 def _getDependsMethod(self, service):
138 return getattr(self, 'depends_%s' % (service.upper(),))
139
140
141 def deploy(Class, name=None, uid=None, gid=None, **kw):
142 """Create an application with the give name, uid, and gid.
143
144 The application has one child service, an instance of Class
145 configured based on the additional keyword arguments passed.
146
147 The application is not persistable.
148 """
149 svc = Class(**kw)
150
151 if name is None:
152 name = Class.__name__
153 # Make it easier (possible) to find this service by name later on
154 svc.setName(name)
155
156 app = service.Application(name, uid=uid, gid=gid)
157 app.addComponent(NotPersistable(app), ignoreClass=True)
158 svc.setServiceParent(app)
159
160 return app
161 deploy = classmethod(deploy)
162
163 def attach(self, subservice):
164 subservice.setServiceParent(self)
165 return subservice
166
167 def detach(self, subservice):
168 subservice.disownServiceParent()
169
170 def addServer(self, normalPort, sslPort, f, name):
171 """Add a TCP and an SSL server. Name them `name` and `name`+'s'."""
172 tcp = internet.TCPServer(normalPort,f)
173 tcp.setName(name)
174 self.servers.append(tcp)
175 if sslPort is not None:
176 ssl = internet.SSLServer(sslPort, f, contextFactory=self.sslfac)
177 ssl.setName(name+'s')
178 self.servers.append(ssl)
179
180 def discernPrivilegedServers(self):
181 return [srv for srv in self.servers if srv.args[0] <= 1024]
182
183 def discernUnprivilegedServers(self):
184 return [srv for srv in self.servers if srv.args[0] > 1024]
185
186 def privilegedStartService(self):
187 for server in self.discernPrivilegedServers():
188 log.msg("privileged attach %r" % server)
189 self.attach(server)
190 return service.MultiService.privilegedStartService(self)
191
192 def startService(self):
193 for server in self.discernUnprivilegedServers():
194 log.msg("attaching %r" % server)
195 self.attach(server)
196
197 return service.MultiService.startService(self)
1980
=== removed file 'Vertex/vertex/endpoint.py'
--- Vertex/vertex/endpoint.py 2005-08-05 06:02:56 +0000
+++ Vertex/vertex/endpoint.py 1970-01-01 00:00:00 +0000
@@ -1,56 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2
3def stablesort(self, other):
4 return cmp(self.__class__, getattr(other, '__class__', type(other)))
5
6class TCPEndpoint:
7 def __init__(self, host, port):
8 self.host = host
9 self.port = port
10
11 def __hash__(self):
12 return hash((self.host, self.port)) + 5
13
14 def connect(self, protocolFactory):
15 from twisted.internet import reactor
16 return reactor.connectTCP(self.host, self.port, protocolFactory)
17
18 def __repr__(self):
19 return '<TCP@%s,%d>' % (self.host, self.port)
20
21 def __cmp__(self, other):
22 if isinstance(other, TCPEndpoint):
23 return cmp((self.host, self.port),
24 (other.host, other.port))
25 return stablesort(self, other)
26
27
28class Q2QEndpoint:
29 def __init__(self, service, fromAddress, toAddress, protocolName):
30 self.service = service
31 self.fromAddress = fromAddress
32 self.toAddress = toAddress
33 self.protocolName = protocolName
34
35 def __repr__(self):
36 return '<Q2Q from <%s> to <%s> on %r>' % (
37 self.fromAddress, self.toAddress, self.protocolName)
38
39 def __cmp__(self, other):
40 if isinstance(other, Q2QEndpoint):
41 return cmp((self.fromAddress, self.toAddress, self.protocolName),
42 (other.fromAddress, other.toAddress, other.protocolName))
43 return stablesort(self, other)
44
45 def __hash__(self):
46 return hash((self.fromAddress,
47 self.toAddress,
48 self.protocolName)) + 7
49
50 def connect(self, protocolFactory):
51 # from twisted.python.context import get
52 # get("q2q-service")
53 return self.service.connectQ2Q(
54 self.fromAddress, self.toAddress, self.protocolName,
55 protocolFactory)
56
570
=== removed file 'Vertex/vertex/gtk2hack.glade'
--- Vertex/vertex/gtk2hack.glade 2006-06-01 04:57:02 +0000
+++ Vertex/vertex/gtk2hack.glade 1970-01-01 00:00:00 +0000
@@ -1,635 +0,0 @@
1<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
2<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
3
4<glade-interface>
5<requires lib="gnome"/>
6
7<widget class="GtkMenu" id="notification_popup">
8
9 <child>
10 <widget class="GtkImageMenuItem" id="add_contact1">
11 <property name="visible">True</property>
12 <property name="label" translatable="yes">Add Contact</property>
13 <property name="use_underline">True</property>
14 <signal name="activate" handler="addContact" last_modification_time="Sun, 22 May 2005 01:24:07 GMT"/>
15
16 <child internal-child="image">
17 <widget class="GtkImage" id="image9">
18 <property name="visible">True</property>
19 <property name="stock">gtk-add</property>
20 <property name="icon_size">1</property>
21 <property name="xalign">0.5</property>
22 <property name="yalign">0.5</property>
23 <property name="xpad">0</property>
24 <property name="ypad">0</property>
25 </widget>
26 </child>
27 </widget>
28 </child>
29
30 <child>
31 <widget class="GtkMenuItem" id="identifymenuitem">
32 <property name="visible">True</property>
33 <property name="label" translatable="yes">Identify</property>
34 <property name="use_underline">True</property>
35 <signal name="activate" handler="identifyDialog" last_modification_time="Sun, 22 May 2005 03:20:18 GMT"/>
36 </widget>
37 </child>
38
39 <child>
40 <widget class="GtkSeparatorMenuItem" id="contacts_begin">
41 <property name="visible">True</property>
42 </widget>
43 </child>
44
45 <child>
46 <widget class="GtkSeparatorMenuItem" id="separator3">
47 <property name="visible">True</property>
48 </widget>
49 </child>
50
51 <child>
52 <widget class="GtkMenuItem" id="animate">
53 <property name="visible">True</property>
54 <property name="label" translatable="yes">Animate</property>
55 <property name="use_underline">True</property>
56 <signal name="activate" handler="toggleAnimate" last_modification_time="Sun, 22 May 2005 01:44:10 GMT"/>
57 </widget>
58 </child>
59
60 <child>
61 <widget class="GtkSeparatorMenuItem" id="separator1">
62 <property name="visible">True</property>
63 </widget>
64 </child>
65
66 <child>
67 <widget class="GtkImageMenuItem" id="quit1">
68 <property name="visible">True</property>
69 <property name="stock_item">GNOMEUIINFO_MENU_EXIT_ITEM</property>
70 <signal name="activate" handler="quit" last_modification_time="Sun, 22 May 2005 01:24:07 GMT"/>
71 </widget>
72 </child>
73</widget>
74
75<widget class="GtkDialog" id="ident_dialog">
76 <property name="visible">True</property>
77 <property name="title" translatable="yes">Identify Yourself</property>
78 <property name="type">GTK_WINDOW_TOPLEVEL</property>
79 <property name="window_position">GTK_WIN_POS_CENTER</property>
80 <property name="modal">False</property>
81 <property name="resizable">False</property>
82 <property name="destroy_with_parent">False</property>
83 <property name="decorated">True</property>
84 <property name="skip_taskbar_hint">False</property>
85 <property name="skip_pager_hint">False</property>
86 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
87 <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
88 <property name="focus_on_map">True</property>
89 <property name="urgency_hint">False</property>
90 <property name="has_separator">True</property>
91
92 <child internal-child="vbox">
93 <widget class="GtkVBox" id="dialog-vbox1">
94 <property name="visible">True</property>
95 <property name="homogeneous">False</property>
96 <property name="spacing">0</property>
97
98 <child internal-child="action_area">
99 <widget class="GtkHButtonBox" id="dialog-action_area1">
100 <property name="visible">True</property>
101 <property name="layout_style">GTK_BUTTONBOX_END</property>
102
103 <child>
104 <widget class="GtkButton" id="cancelbutton1">
105 <property name="visible">True</property>
106 <property name="can_default">True</property>
107 <property name="can_focus">True</property>
108 <property name="label">gtk-cancel</property>
109 <property name="use_stock">True</property>
110 <property name="relief">GTK_RELIEF_NORMAL</property>
111 <property name="focus_on_click">True</property>
112 <property name="response_id">-6</property>
113 <signal name="clicked" handler="identifyCancel" last_modification_time="Sun, 22 May 2005 03:14:51 GMT"/>
114 </widget>
115 </child>
116
117 <child>
118 <widget class="GtkButton" id="okbutton1">
119 <property name="visible">True</property>
120 <property name="can_default">True</property>
121 <property name="can_focus">True</property>
122 <property name="label">gtk-ok</property>
123 <property name="use_stock">True</property>
124 <property name="relief">GTK_RELIEF_NORMAL</property>
125 <property name="focus_on_click">True</property>
126 <property name="response_id">-5</property>
127 <signal name="clicked" handler="identifyOK" last_modification_time="Sun, 22 May 2005 03:15:00 GMT"/>
128 </widget>
129 </child>
130 </widget>
131 <packing>
132 <property name="padding">0</property>
133 <property name="expand">False</property>
134 <property name="fill">True</property>
135 <property name="pack_type">GTK_PACK_END</property>
136 </packing>
137 </child>
138
139 <child>
140 <widget class="GtkVBox" id="vbox1">
141 <property name="visible">True</property>
142 <property name="homogeneous">False</property>
143 <property name="spacing">0</property>
144
145 <child>
146 <widget class="GtkTable" id="table1">
147 <property name="border_width">5</property>
148 <property name="visible">True</property>
149 <property name="n_rows">2</property>
150 <property name="n_columns">2</property>
151 <property name="homogeneous">False</property>
152 <property name="row_spacing">2</property>
153 <property name="column_spacing">5</property>
154
155 <child>
156 <widget class="GtkLabel" id="label4">
157 <property name="visible">True</property>
158 <property name="label" translatable="yes">Address</property>
159 <property name="use_underline">False</property>
160 <property name="use_markup">False</property>
161 <property name="justify">GTK_JUSTIFY_LEFT</property>
162 <property name="wrap">False</property>
163 <property name="selectable">False</property>
164 <property name="xalign">0</property>
165 <property name="yalign">0.5</property>
166 <property name="xpad">0</property>
167 <property name="ypad">0</property>
168 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
169 <property name="width_chars">-1</property>
170 <property name="single_line_mode">False</property>
171 <property name="angle">0</property>
172 </widget>
173 <packing>
174 <property name="left_attach">0</property>
175 <property name="right_attach">1</property>
176 <property name="top_attach">0</property>
177 <property name="bottom_attach">1</property>
178 <property name="x_options">fill</property>
179 <property name="y_options"></property>
180 </packing>
181 </child>
182
183 <child>
184 <widget class="GtkLabel" id="label5">
185 <property name="visible">True</property>
186 <property name="label" translatable="yes">Password</property>
187 <property name="use_underline">False</property>
188 <property name="use_markup">False</property>
189 <property name="justify">GTK_JUSTIFY_LEFT</property>
190 <property name="wrap">False</property>
191 <property name="selectable">False</property>
192 <property name="xalign">0</property>
193 <property name="yalign">0.5</property>
194 <property name="xpad">0</property>
195 <property name="ypad">0</property>
196 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
197 <property name="width_chars">-1</property>
198 <property name="single_line_mode">False</property>
199 <property name="angle">0</property>
200 </widget>
201 <packing>
202 <property name="left_attach">0</property>
203 <property name="right_attach">1</property>
204 <property name="top_attach">1</property>
205 <property name="bottom_attach">2</property>
206 <property name="x_options">fill</property>
207 <property name="y_options"></property>
208 </packing>
209 </child>
210
211 <child>
212 <widget class="GtkEntry" id="addressEntry">
213 <property name="visible">True</property>
214 <property name="can_focus">True</property>
215 <property name="has_focus">True</property>
216 <property name="editable">True</property>
217 <property name="visibility">True</property>
218 <property name="max_length">0</property>
219 <property name="text" translatable="yes"></property>
220 <property name="has_frame">True</property>
221 <property name="invisible_char">*</property>
222 <property name="activates_default">False</property>
223 </widget>
224 <packing>
225 <property name="left_attach">1</property>
226 <property name="right_attach">2</property>
227 <property name="top_attach">0</property>
228 <property name="bottom_attach">1</property>
229 <property name="y_options"></property>
230 </packing>
231 </child>
232
233 <child>
234 <widget class="GtkEntry" id="passwordEntry">
235 <property name="visible">True</property>
236 <property name="can_focus">True</property>
237 <property name="editable">True</property>
238 <property name="visibility">False</property>
239 <property name="max_length">0</property>
240 <property name="text" translatable="yes"></property>
241 <property name="has_frame">True</property>
242 <property name="invisible_char">*</property>
243 <property name="activates_default">True</property>
244 </widget>
245 <packing>
246 <property name="left_attach">1</property>
247 <property name="right_attach">2</property>
248 <property name="top_attach">1</property>
249 <property name="bottom_attach">2</property>
250 <property name="y_options"></property>
251 </packing>
252 </child>
253 </widget>
254 <packing>
255 <property name="padding">0</property>
256 <property name="expand">True</property>
257 <property name="fill">True</property>
258 </packing>
259 </child>
260
261 <child>
262 <widget class="GtkProgressBar" id="identifyProgressBar">
263 <property name="visible">True</property>
264 <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
265 <property name="fraction">0</property>
266 <property name="pulse_step">0.10000000149</property>
267 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
268 </widget>
269 <packing>
270 <property name="padding">0</property>
271 <property name="expand">False</property>
272 <property name="fill">False</property>
273 </packing>
274 </child>
275
276 <child>
277 <widget class="GtkLabel" id="identifyProgressLabel">
278 <property name="visible">True</property>
279 <property name="label" translatable="yes"></property>
280 <property name="use_underline">False</property>
281 <property name="use_markup">False</property>
282 <property name="justify">GTK_JUSTIFY_LEFT</property>
283 <property name="wrap">False</property>
284 <property name="selectable">False</property>
285 <property name="xalign">0.5</property>
286 <property name="yalign">0.5</property>
287 <property name="xpad">0</property>
288 <property name="ypad">0</property>
289 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
290 <property name="width_chars">-1</property>
291 <property name="single_line_mode">False</property>
292 <property name="angle">0</property>
293 </widget>
294 <packing>
295 <property name="padding">3</property>
296 <property name="expand">False</property>
297 <property name="fill">False</property>
298 </packing>
299 </child>
300 </widget>
301 <packing>
302 <property name="padding">0</property>
303 <property name="expand">False</property>
304 <property name="fill">True</property>
305 </packing>
306 </child>
307 </widget>
308 </child>
309</widget>
310
311<widget class="GtkDialog" id="add_contact_dialog">
312 <property name="visible">True</property>
313 <property name="title" translatable="yes">Add Contact</property>
314 <property name="type">GTK_WINDOW_TOPLEVEL</property>
315 <property name="window_position">GTK_WIN_POS_NONE</property>
316 <property name="modal">False</property>
317 <property name="resizable">True</property>
318 <property name="destroy_with_parent">False</property>
319 <property name="decorated">True</property>
320 <property name="skip_taskbar_hint">False</property>
321 <property name="skip_pager_hint">False</property>
322 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
323 <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
324 <property name="focus_on_map">True</property>
325 <property name="urgency_hint">False</property>
326 <property name="has_separator">True</property>
327
328 <child internal-child="vbox">
329 <widget class="GtkVBox" id="dialog-vbox2">
330 <property name="visible">True</property>
331 <property name="homogeneous">False</property>
332 <property name="spacing">0</property>
333
334 <child internal-child="action_area">
335 <widget class="GtkHButtonBox" id="dialog-action_area2">
336 <property name="visible">True</property>
337 <property name="layout_style">GTK_BUTTONBOX_END</property>
338
339 <child>
340 <widget class="GtkButton" id="cancelbutton2">
341 <property name="visible">True</property>
342 <property name="can_default">True</property>
343 <property name="can_focus">True</property>
344 <property name="label">gtk-cancel</property>
345 <property name="use_stock">True</property>
346 <property name="relief">GTK_RELIEF_NORMAL</property>
347 <property name="focus_on_click">True</property>
348 <property name="response_id">-6</property>
349 <signal name="activate" handler="popdownDialog" last_modification_time="Tue, 28 Feb 2006 09:34:42 GMT"/>
350 </widget>
351 </child>
352
353 <child>
354 <widget class="GtkButton" id="okbutton2">
355 <property name="visible">True</property>
356 <property name="can_default">True</property>
357 <property name="can_focus">True</property>
358 <property name="label">gtk-ok</property>
359 <property name="use_stock">True</property>
360 <property name="relief">GTK_RELIEF_NORMAL</property>
361 <property name="focus_on_click">True</property>
362 <property name="response_id">-5</property>
363 <signal name="activate" handler="doAddContact" last_modification_time="Tue, 28 Feb 2006 09:34:23 GMT"/>
364 <signal name="clicked" handler="doAddContact" last_modification_time="Tue, 28 Feb 2006 10:36:25 GMT"/>
365 </widget>
366 </child>
367 </widget>
368 <packing>
369 <property name="padding">0</property>
370 <property name="expand">False</property>
371 <property name="fill">True</property>
372 <property name="pack_type">GTK_PACK_END</property>
373 </packing>
374 </child>
375
376 <child>
377 <widget class="GtkFrame" id="frame1">
378 <property name="visible">True</property>
379 <property name="label_xalign">0</property>
380 <property name="label_yalign">0.5</property>
381 <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
382
383 <child>
384 <widget class="GtkAlignment" id="alignment1">
385 <property name="visible">True</property>
386 <property name="xalign">0.5</property>
387 <property name="yalign">0.5</property>
388 <property name="xscale">1</property>
389 <property name="yscale">1</property>
390 <property name="top_padding">0</property>
391 <property name="bottom_padding">0</property>
392 <property name="left_padding">12</property>
393 <property name="right_padding">0</property>
394
395 <child>
396 <widget class="GtkTable" id="table2">
397 <property name="border_width">16</property>
398 <property name="visible">True</property>
399 <property name="n_rows">2</property>
400 <property name="n_columns">2</property>
401 <property name="homogeneous">False</property>
402 <property name="row_spacing">16</property>
403 <property name="column_spacing">16</property>
404
405 <child>
406 <widget class="GtkEntry" id="nameentry">
407 <property name="visible">True</property>
408 <property name="can_focus">True</property>
409 <property name="editable">True</property>
410 <property name="visibility">True</property>
411 <property name="max_length">0</property>
412 <property name="text" translatable="yes"></property>
413 <property name="has_frame">True</property>
414 <property name="invisible_char">*</property>
415 <property name="activates_default">False</property>
416 </widget>
417 <packing>
418 <property name="left_attach">1</property>
419 <property name="right_attach">2</property>
420 <property name="top_attach">0</property>
421 <property name="bottom_attach">1</property>
422 <property name="y_options"></property>
423 </packing>
424 </child>
425
426 <child>
427 <widget class="GtkEntry" id="q2qidentry">
428 <property name="visible">True</property>
429 <property name="can_focus">True</property>
430 <property name="editable">True</property>
431 <property name="visibility">True</property>
432 <property name="max_length">0</property>
433 <property name="text" translatable="yes"></property>
434 <property name="has_frame">True</property>
435 <property name="invisible_char">*</property>
436 <property name="activates_default">False</property>
437 </widget>
438 <packing>
439 <property name="left_attach">1</property>
440 <property name="right_attach">2</property>
441 <property name="top_attach">1</property>
442 <property name="bottom_attach">2</property>
443 <property name="y_options"></property>
444 </packing>
445 </child>
446
447 <child>
448 <widget class="GtkLabel" id="label7">
449 <property name="visible">True</property>
450 <property name="label" translatable="yes">Name</property>
451 <property name="use_underline">False</property>
452 <property name="use_markup">False</property>
453 <property name="justify">GTK_JUSTIFY_LEFT</property>
454 <property name="wrap">False</property>
455 <property name="selectable">False</property>
456 <property name="xalign">0</property>
457 <property name="yalign">0.5</property>
458 <property name="xpad">0</property>
459 <property name="ypad">0</property>
460 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
461 <property name="width_chars">-1</property>
462 <property name="single_line_mode">False</property>
463 <property name="angle">0</property>
464 </widget>
465 <packing>
466 <property name="left_attach">0</property>
467 <property name="right_attach">1</property>
468 <property name="top_attach">0</property>
469 <property name="bottom_attach">1</property>
470 <property name="x_options">fill</property>
471 <property name="y_options"></property>
472 </packing>
473 </child>
474
475 <child>
476 <widget class="GtkLabel" id="label8">
477 <property name="visible">True</property>
478 <property name="label" translatable="yes">Q2QID</property>
479 <property name="use_underline">False</property>
480 <property name="use_markup">False</property>
481 <property name="justify">GTK_JUSTIFY_LEFT</property>
482 <property name="wrap">False</property>
483 <property name="selectable">False</property>
484 <property name="xalign">0</property>
485 <property name="yalign">0.5</property>
486 <property name="xpad">0</property>
487 <property name="ypad">0</property>
488 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
489 <property name="width_chars">-1</property>
490 <property name="single_line_mode">False</property>
491 <property name="angle">0</property>
492 </widget>
493 <packing>
494 <property name="left_attach">0</property>
495 <property name="right_attach">1</property>
496 <property name="top_attach">1</property>
497 <property name="bottom_attach">2</property>
498 <property name="x_options">fill</property>
499 <property name="y_options"></property>
500 </packing>
501 </child>
502 </widget>
503 </child>
504 </widget>
505 </child>
506
507 <child>
508 <widget class="GtkLabel" id="label6">
509 <property name="visible">True</property>
510 <property name="label" translatable="yes">Contact Information</property>
511 <property name="use_underline">False</property>
512 <property name="use_markup">True</property>
513 <property name="justify">GTK_JUSTIFY_LEFT</property>
514 <property name="wrap">False</property>
515 <property name="selectable">False</property>
516 <property name="xalign">0.5</property>
517 <property name="yalign">0.5</property>
518 <property name="xpad">0</property>
519 <property name="ypad">0</property>
520 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
521 <property name="width_chars">-1</property>
522 <property name="single_line_mode">False</property>
523 <property name="angle">0</property>
524 </widget>
525 <packing>
526 <property name="type">label_item</property>
527 </packing>
528 </child>
529 </widget>
530 <packing>
531 <property name="padding">0</property>
532 <property name="expand">True</property>
533 <property name="fill">True</property>
534 </packing>
535 </child>
536 </widget>
537 </child>
538</widget>
539
540<widget class="GtkDialog" id="accept_connection_dialog">
541 <property name="visible">True</property>
542 <property name="title" translatable="yes">Accept Connection?</property>
543 <property name="type">GTK_WINDOW_TOPLEVEL</property>
544 <property name="window_position">GTK_WIN_POS_NONE</property>
545 <property name="modal">False</property>
546 <property name="resizable">True</property>
547 <property name="destroy_with_parent">False</property>
548 <property name="decorated">True</property>
549 <property name="skip_taskbar_hint">False</property>
550 <property name="skip_pager_hint">False</property>
551 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
552 <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
553 <property name="focus_on_map">True</property>
554 <property name="urgency_hint">False</property>
555 <property name="has_separator">True</property>
556 <signal name="destroy" handler="rejectConnectionEvt" last_modification_time="Tue, 28 Feb 2006 10:00:37 GMT"/>
557
558 <child internal-child="vbox">
559 <widget class="GtkVBox" id="dialog-vbox3">
560 <property name="visible">True</property>
561 <property name="homogeneous">False</property>
562 <property name="spacing">0</property>
563
564 <child internal-child="action_area">
565 <widget class="GtkHButtonBox" id="dialog-action_area3">
566 <property name="visible">True</property>
567 <property name="layout_style">GTK_BUTTONBOX_END</property>
568
569 <child>
570 <widget class="GtkButton" id="cancelbutton3">
571 <property name="visible">True</property>
572 <property name="can_default">True</property>
573 <property name="can_focus">True</property>
574 <property name="label">gtk-cancel</property>
575 <property name="use_stock">True</property>
576 <property name="relief">GTK_RELIEF_NORMAL</property>
577 <property name="focus_on_click">True</property>
578 <property name="response_id">-6</property>
579 <signal name="activate" handler="destroyit" last_modification_time="Tue, 28 Feb 2006 10:00:55 GMT"/>
580 <signal name="clicked" handler="destroyit" last_modification_time="Tue, 28 Feb 2006 10:36:09 GMT"/>
581 </widget>
582 </child>
583
584 <child>
585 <widget class="GtkButton" id="okbutton3">
586 <property name="visible">True</property>
587 <property name="can_default">True</property>
588 <property name="can_focus">True</property>
589 <property name="label">gtk-ok</property>
590 <property name="use_stock">True</property>
591 <property name="relief">GTK_RELIEF_NORMAL</property>
592 <property name="focus_on_click">True</property>
593 <property name="response_id">-5</property>
594 <signal name="activate" handler="acceptConnectionEvt" last_modification_time="Tue, 28 Feb 2006 09:59:48 GMT"/>
595 <signal name="clicked" handler="acceptConnectionEvt" last_modification_time="Tue, 28 Feb 2006 10:36:51 GMT"/>
596 </widget>
597 </child>
598 </widget>
599 <packing>
600 <property name="padding">0</property>
601 <property name="expand">False</property>
602 <property name="fill">True</property>
603 <property name="pack_type">GTK_PACK_END</property>
604 </packing>
605 </child>
606
607 <child>
608 <widget class="GtkLabel" id="accept_connection_label">
609 <property name="visible">True</property>
610 <property name="label" translatable="yes">Accept connection?</property>
611 <property name="use_underline">False</property>
612 <property name="use_markup">False</property>
613 <property name="justify">GTK_JUSTIFY_LEFT</property>
614 <property name="wrap">False</property>
615 <property name="selectable">False</property>
616 <property name="xalign">0.5</property>
617 <property name="yalign">0.5</property>
618 <property name="xpad">0</property>
619 <property name="ypad">0</property>
620 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
621 <property name="width_chars">-1</property>
622 <property name="single_line_mode">False</property>
623 <property name="angle">0</property>
624 </widget>
625 <packing>
626 <property name="padding">0</property>
627 <property name="expand">False</property>
628 <property name="fill">False</property>
629 </packing>
630 </child>
631 </widget>
632 </child>
633</widget>
634
635</glade-interface>
6360
=== removed file 'Vertex/vertex/gtk2hack.py'
--- Vertex/vertex/gtk2hack.py 2006-06-01 04:57:02 +0000
+++ Vertex/vertex/gtk2hack.py 1970-01-01 00:00:00 +0000
@@ -1,270 +0,0 @@
1
2import os
3import rfc822
4
5from twisted.python.filepath import FilePath
6
7# import gtk ### pyflakes complains about this, due to the next line
8import gtk.glade
9
10from vertex.q2qclient import ClientQ2QService
11from vertex.q2q import Q2QAddress
12
13class _NullCb:
14 def __init__(self, name):
15 self.name = name
16
17 def __call__(self, *a, **kw):
18 print 'No callback provided for', self.name, a, kw
19
20class _SignalAttacher:
21 def __init__(self, original):
22 self.original = original
23
24 def __getitem__(self, callbackName):
25 return getattr(self.original, callbackName, None) or _NullCb(callbackName)
26
27GLADE_FILE = os.path.splitext(__file__)[0] + '.glade'
28
29class IdentificationDialog:
30 def __init__(self, clientService, plug):
31 self.xml = gtk.glade.XML(GLADE_FILE, "ident_dialog")
32 self.clientService = clientService
33 self.xml.signal_autoconnect(_SignalAttacher(self))
34 self.addressEntry = self.xml.get_widget('addressEntry')
35 self.passwordEntry = self.xml.get_widget('passwordEntry')
36 self.progressBar = self.xml.get_widget('identifyProgressBar')
37 self.progressLabel = self.xml.get_widget('identifyProgressLabel')
38 self.identifyWindow = self.xml.get_widget("ident_dialog")
39 self.cancelButton = self.xml.get_widget('cancelbutton1')
40 self.okButton = self.xml.get_widget('okbutton1')
41 self.plug = plug
42
43 def identifyCancel(self, event):
44 self.identifyWindow.destroy()
45
46 def identifyOK(self, event):
47 idstr = self.addressEntry.get_text()
48 D = self.clientService.authorize(
49 Q2QAddress.fromString(idstr),
50 self.passwordEntry.get_text())
51
52 sensitiveWidgets = [self.addressEntry,
53 self.passwordEntry,
54 self.okButton,
55 self.cancelButton]
56 for widget in sensitiveWidgets:
57 widget.set_sensitive(False)
58 self.progressLabel.set_text("Authenticating...")
59 def itWorked(workedNone):
60 self.identifyWindow.destroy()
61 self.plug.setCurrentID(idstr)
62 def itDidntWork(error):
63 self.progressLabel.set_text(error.getErrorMessage())
64 for widget in sensitiveWidgets:
65 widget.set_sensitive(True)
66 D.addCallbacks(itWorked, itDidntWork)
67
68class AddContactDialog:
69 def __init__(self, plug):
70 self.xml = gtk.glade.XML(GLADE_FILE, "add_contact_dialog")
71 self.xml.signal_autoconnect(_SignalAttacher(self))
72 self.window = self.xml.get_widget("add_contact_dialog")
73 self.window.show_all()
74 self.plug = plug
75
76 def doAddContact(self, evt):
77 name = self.xml.get_widget("nameentry").get_text()
78 addr = self.xml.get_widget("q2qidentry").get_text()
79 self.plug.addBuddy(name, addr)
80 self.popdownDialog()
81
82 def popdownDialog(self, evt=None):
83 self.window.destroy()
84
85class AcceptConnectionDialog:
86 def __init__(self, d, From, to, protocol):
87 self.d = d
88 self.xml = gtk.glade.XML(GLADE_FILE, "accept_connection_dialog")
89 self.xml.signal_autoconnect(_SignalAttacher(self))
90 self.label = self.xml.get_widget("accept_connection_label")
91 self.label.set_text(
92 "Accept connection from %s for %s?" % (From, protocol))
93 self.window = self.xml.get_widget("accept_connection_dialog")
94 self.window.show_all()
95
96 done = False
97
98 def destroyit(self, evt):
99 self.window.destroy()
100
101 def acceptConnectionEvt(self, evt):
102 self.done = True
103 print "YES"
104 self.d.callback(1)
105 print "WHAT"
106 self.window.destroy()
107
108 def rejectConnectionEvt(self, evt):
109 print "DSTRY"
110 if not self.done:
111 print "DIE!"
112 from twisted.python import failure
113 self.d.errback(failure.Failure(KeyError("Connection rejected by user")))
114 else:
115 print "OK"
116
117from twisted.internet.protocol import ServerFactory
118from twisted.internet.protocol import Protocol
119
120class VertexDemoProtocol(Protocol):
121
122 def connectionMade(self):
123 print 'CONN MADE'
124
125 def dataReceived(self, data):
126 print 'HOLY SHNIKIES', data
127
128class VertexFactory(ServerFactory):
129 protocol = VertexDemoProtocol
130
131 def __init__(self, plug):
132 self.plug = plug
133
134 def startFactory(self):
135 #self.plug.animator.stop(1)
136 pass
137
138 def stopFactory(self):
139 #self.plug.animator.stop(0)
140 pass
141
142
143class BuddyItem:
144 def __init__(self, plug, alias, q2qaddress):
145 mi = self.menuItem = gtk.MenuItem(alias + " <"+q2qaddress+">")
146 mi.connect("activate", self.initiateFileTransfer)
147 mi.show_all()
148 self.plug = plug
149 self.alias = alias
150 self.q2qaddress = q2qaddress
151 self.plug.loadedBuddies[q2qaddress] = self
152
153 def initiateFileTransfer(self, evt):
154 print 'Initiate transfer with ' + self.alias + self.q2qaddress
155
156 def addToMenu(self):
157 self.plug.section.append(self.menuItem)
158
159 def removeFromMenu(self):
160 self.plug.section.remove(self.menuItem)
161
162from twisted.plugin import IPlugin
163from prime.iprime import IMenuApplication
164from zope.interface import implements
165
166class PlugEntry:
167 implements(IMenuApplication, IPlugin)
168
169 def __init__(self):
170 self.xml = gtk.glade.XML(GLADE_FILE, "notification_popup")
171
172 def register(self, section):
173 print 'REGISTER'
174 self.section = section
175
176 workingdir = FilePath(os.path.expanduser("~/.vertex"))
177 self.clientService = ClientQ2QService(
178 workingdir.child("q2q-certificates").path,
179 verifyHook=self.displayVerifyDialog,
180 inboundTCPPortnum=8172,
181 # q2qPortnum=8173,
182 udpEnabled=False)
183 self.setCurrentID(self.clientService.getDefaultFrom())
184 self.buddiesfile = workingdir.child("q2q-buddies.txt")
185 self.loadedBuddies = {}
186 self.parseBuddies()
187
188 def parseBuddies(self):
189 try:
190 self.buddyList = rfc822.AddressList(self.buddiesfile.open().read())
191 except IOError:
192 return
193 self.clearContactMenu()
194 for dispn, addr in self.buddyList:
195 if addr not in self.loadedBuddies:
196 BuddyItem(self, dispn, addr)
197 self.buildContactMenu()
198
199 def clearContactMenu(self):
200 for bud in self.loadedBuddies.values():
201 bud.removeFromMenu()
202
203 def buildContactMenu(self):
204 l = self.loadedBuddies.values()
205 l.sort(key=lambda x: x.alias)
206 l.reverse()
207 for bud in l:
208 bud.addToMenu()
209
210 def addBuddy(self, alias, q2qaddr):
211 temp = self.buddiesfile.temporarySibling()
212 try:
213 origdata = self.buddiesfile.open().read()
214 except IOError:
215 origdata = ''
216 moredata = '\n%s <%s>' % (alias, q2qaddr)
217 ftemp = temp.open('w')
218 ftemp.write(origdata)
219 ftemp.write(moredata)
220 ftemp.close()
221 temp.moveTo(self.buddiesfile)
222 self.parseBuddies()
223
224 def displayVerifyDialog(self, From, to, protocol):
225 from twisted.internet import defer
226 d = defer.Deferred()
227 AcceptConnectionDialog(d, From, to, protocol)
228 return d
229
230 def setCurrentID(self, idName):
231
232 if idName is not None:
233 currentID = Q2QAddress.fromString(idName)
234 # log in?
235 # self.animator.start()
236 SL = self.xml.get_widget("identifymenuitem").get_children()[0].set_label
237 def loggedIn(result):
238 SL(str(currentID))
239 self.currentID = currentID
240 def notLoggedIn(error):
241 SL("Identify")
242 # self.animator.stop(0)
243 # This following order is INSANE - you should definitely not have
244 # to wait until the LISTEN succeeds to start the service; quite the
245 # opposite, you should wait until the service has started, then
246 # issue the LISTEN!! For some reason, the connection drops
247 # immediately if you do that, and I have no idea why. As soon as I
248 # can fix that issue the startService should be moved up previous
249 # to listenQ2Q.
250 self.clientService.listenQ2Q(currentID,
251 {'vertex': VertexFactory(self)},
252 "desktop vertex UI").addCallbacks(
253 loggedIn, notLoggedIn).addCallback(
254 lambda ign: self.clientService.startService())
255
256 # XXX event handlers
257
258 def toggleAnimate(self, event):
259 if self.animator.animating:
260 # SL("Animate")
261 self.animator.stop()
262 else:
263 # SL("Stop Animating")
264 self.animator.start()
265
266 def identifyDialog(self, event):
267 IdentificationDialog(self.clientService, self)
268
269 def addContact(self, event):
270 AddContactDialog(self)
2710
=== removed file 'Vertex/vertex/icon-active.png'
272Binary files Vertex/vertex/icon-active.png 2006-06-01 04:57:02 +0000 and Vertex/vertex/icon-active.png 1970-01-01 00:00:00 +0000 differ1Binary files Vertex/vertex/icon-active.png 2006-06-01 04:57:02 +0000 and Vertex/vertex/icon-active.png 1970-01-01 00:00:00 +0000 differ
=== removed file 'Vertex/vertex/icon-inactive.png'
273Binary files Vertex/vertex/icon-inactive.png 2006-06-01 04:57:02 +0000 and Vertex/vertex/icon-inactive.png 1970-01-01 00:00:00 +0000 differ2Binary files Vertex/vertex/icon-inactive.png 2006-06-01 04:57:02 +0000 and Vertex/vertex/icon-inactive.png 1970-01-01 00:00:00 +0000 differ
=== removed file 'Vertex/vertex/ivertex.py'
--- Vertex/vertex/ivertex.py 2012-03-14 16:23:22 +0000
+++ Vertex/vertex/ivertex.py 1970-01-01 00:00:00 +0000
@@ -1,108 +0,0 @@
1# Copyright 2005 Divmod, Inc. See LICENSE file for details
2
3from zope.interface import Interface
4
5class IQ2QTransport(Interface):
6 """
7 I am a byte-stream-oriented transport which has Q2Q identifiers associated
8 with the endpoints, and possibly some cryptographic verification of the
9 authenticity of those endpoints.
10 """
11
12 def getQ2QHost():
13 """ Returns a Q2QAddress object representing the user on this end of the
14 connection.
15 """
16
17 def getQ2QPeer():
18 """ Returns a Q2QAddress object representing the user on the other end of the
19 connection.
20 """
21
22class IQ2QUser(Interface):
23 """
24 A cred interface for Q2Q users.
25 """
26 def signCertificateRequest(certificateRequest, domainCert, suggestedSerial):
27 """
28 Return a signed certificate object if the subject fields in the
29 certificateRequest are valid.
30 """
31
32class IFileTransfer(Interface):
33
34 def getUploadSink(self, path):
35 """
36 @param path: a PathFragment that the client wishes to upload to.
37
38 @return: a DataSink where we'll save the data to.
39 """
40
41 def getDownloadSource(self, path):
42 """
43 @param path: a PathFragment that the client wishes to download.
44
45 @return: a DataSource to download data from.
46 """
47
48 def listChildren(self, path):
49 """
50 @param path: a PathFragment that the client wishes to get a list of.
51
52 @return: a list of dictionaries mapping::
53 {'name': str,
54 'size': int,
55 'type': vertex.filexfer.MIMEType,
56 'modified': datetime.datetime}
57 """
58
59class ISessionTokenStorage(Interface):
60 def idFromCookie(self, cookie, domain):
61 """Look up a user ID from the given cookie in the given domain.
62 """
63
64class ICertificateStorage(Interface):
65 def getSelfSignedCertificate(self, domainName):
66 """
67 @return: a Deferred which will fire with the certificate for the given
68 domain name.
69 """
70
71 def storeSelfSignedCertificate(self, domainName, mainCert):
72 """
73 @type mainCert: C{str}
74 @param mainCert: Serialized, self-signed certificate to associate
75 with the given domain.
76
77 @return: a Deferred which will fire when the certificate has been
78 stored successfully.
79 """
80
81 def getPrivateCertificate(self, domainName):
82 """
83 @return: a PrivateCertificate instance, e.g. a certificate including a
84 private key, for 'domainName'.
85 """
86
87 def addPrivateCertificate(self, domainName, existingCertificate=None):
88 """
89 """
90
91class IOfferUp(Interface):
92 """
93 Sharing control database storage.
94 """
95
96class IPlugin(Interface):
97 """
98 """
99
100class ITestPlugin(Interface):
101 """
102 Dummy plug-in interface for unit testing.
103 """
104
105class ITestPlugin2(Interface):
106 """
107 Dummy plug-in interface for unit testing.
108 """
1090
=== removed file 'Vertex/vertex/ptcp.py'
--- Vertex/vertex/ptcp.py 2012-03-14 16:23:22 +0000
+++ Vertex/vertex/ptcp.py 1970-01-01 00:00:00 +0000
@@ -1,1050 +0,0 @@
1# -*- test-case-name: vertex.test.test_ptcp -*-
2
3import struct
4
5from binascii import crc32 # used to use zlib.crc32 - but that gives different
6 # results on 64-bit platforms!!
7
8import itertools
9
10from twisted.python.failure import Failure
11from twisted.internet.defer import Deferred
12from twisted.internet import protocol, error, reactor, defer
13from twisted.internet.main import CONNECTION_DONE
14from twisted.python import log, util
15
16from vertex import tcpdfa
17from vertex.statemachine import StateError
18
19
20genConnID = itertools.count(8).next
21
22MAX_PSEUDO_PORT = (2 ** 16)
23
24_packetFormat = ('!' # WTF did you think
25 'H' # sourcePseudoPort
26 'H' # destPseudoPort
27 'L' # sequenceNumber
28 'L' # acknowledgementNumber
29 'L' # window
30 'B' # flags
31 'l' # checksum
32 # (signed because of binascii.crc32)
33 'H' # dlen
34 )
35_fixedSize = struct.calcsize(_packetFormat)
36
37_SYN, _ACK, _FIN, _RST, _STB = [1 << n for n in range(5)]
38
39def _flagprop(flag):
40 def setter(self, value):
41 if value:
42 self.flags |= flag
43 else:
44 self.flags &= ~flag
45 return property(lambda self: bool(self.flags & flag), setter)
46
47def relativeSequence(wireSequence, initialSequence, lapNumber):
48 """ Compute a relative sequence number from a wire sequence number so that we
49 can use natural Python comparisons on it, such as <, >, ==.
50
51 @param wireSequence: the sequence number received on the wire.
52
53 @param initialSequence: the ISN for this sequence, negotiated at SYN time.
54
55 @param lapNumber: the number of times that this value has wrapped around
56 2**32.
57 """
58 return (wireSequence + (lapNumber * (2**32))) - initialSequence
59
60class PTCPPacket(util.FancyStrMixin, object):
61 showAttributes = (
62 ('sourcePseudoPort', 'sourcePseudoPort', '%d'),
63 ('destPseudoPort', 'destPseudoPort', '%d'),
64 ('shortdata', 'data', '%r'),
65 ('niceflags', 'flags', '%s'),
66 ('dlen', 'dlen', '%d'),
67 ('seqNum', 'seq', '%d'),
68 ('ackNum', 'ack', '%d'),
69 ('checksum', 'checksum', '%x'),
70 ('peerAddressTuple', 'peerAddress', '%r'),
71 ('retransmitCount', 'retransmitCount', '%d'),
72 )
73
74 syn = _flagprop(_SYN)
75 ack = _flagprop(_ACK)
76 fin = _flagprop(_FIN)
77 rst = _flagprop(_RST)
78 stb = _flagprop(_STB)
79
80 # Number of retransmit attempts left for this segment. When it reaches
81 # zero, this segment is dead.
82 retransmitCount = 50
83
84 def shortdata():
85 def get(self):
86 if len(self.data) > 13:
87 return self.data[:5] + '...' + self.data[-5:]
88 else:
89 return self.data
90 return get,
91 shortdata = property(*shortdata())
92
93 def niceflags():
94 def get(self):
95 res = []
96 for (f, v) in [
97 (self.syn, 'S'), (self.ack, 'A'), (self.fin, 'F'),
98 (self.rst, 'R'), (self.stb, 'T')]:
99 res.append(f and v or '.')
100 return ''.join(res)
101 return get,
102 niceflags = property(*niceflags())
103
104 def create(cls,
105 sourcePseudoPort, destPseudoPort,
106 seqNum, ackNum, data,
107 window=(1 << 15),
108 syn=False, ack=False, fin=False,
109 rst=False, stb=False,
110 destination=None):
111 i = cls(sourcePseudoPort, destPseudoPort,
112 seqNum, ackNum, window,
113 0, 0, len(data), data)
114 i.syn = syn
115 i.ack = ack
116 i.fin = fin
117 i.rst = rst
118 i.stb = stb
119 i.checksum = i.computeChecksum()
120 i.destination = destination
121 return i
122 create = classmethod(create)
123
124
125 def __init__(self,
126 sourcePseudoPort,
127 destPseudoPort,
128 seqNum, ackNum, window, flags,
129 checksum, dlen, data, peerAddressTuple=None,
130 seqOffset=0, ackOffset=0, seqLaps=0, ackLaps=0):
131 self.sourcePseudoPort = sourcePseudoPort
132 self.destPseudoPort = destPseudoPort
133 self.seqNum = seqNum
134 self.ackNum = ackNum
135 self.window = window
136 self.flags = flags
137 self.checksum = checksum
138 self.dlen = dlen
139 self.data = data
140 self.peerAddressTuple = peerAddressTuple # None if local
141
142 self.seqOffset = seqOffset
143 self.ackOffset = ackOffset
144 self.seqLaps = seqLaps
145 self.ackLaps = ackLaps
146
147 def segmentLength(self):
148 """RFC page 26: 'The segment length (SEG.LEN) includes both data and sequence
149 space occupying controls'
150 """
151 return self.dlen + self.syn + self.fin
152
153 def relativeSeq(self):
154 return relativeSequence(self.seqNum, self.seqOffset, self.seqLaps)
155
156 def relativeAck(self):
157 return relativeSequence(self.ackNum, self.ackOffset, self.ackLaps)
158
159
160 def verifyChecksum(self):
161 if len(self.data) != self.dlen:
162 if len(self.data) > self.dlen:
163 raise GarbageDataError(self)
164 else:
165 raise TruncatedDataError(self)
166 expected = self.computeChecksum()
167 received = self.checksum
168 if expected != received:
169 raise ChecksumMismatchError(expected, received)
170
171 def computeChecksum(self):
172 return crc32(self.data)
173
174 def decode(cls, bytes, hostPortPair):
175 fields = struct.unpack(_packetFormat, bytes[:_fixedSize])
176 sourcePseudoPort, destPseudoPort, seq, ack, window, flags, checksum, dlen = fields
177 data = bytes[_fixedSize:]
178 pkt = cls(sourcePseudoPort, destPseudoPort, seq, ack, window, flags,
179 checksum, dlen, data, hostPortPair)
180 return pkt
181 decode = classmethod(decode)
182
183 def mustRetransmit(self):
184 """Check to see if this packet must be retransmitted until it was received.
185 """
186 if self.syn or self.fin or self.dlen:
187 return True
188 return False
189
190 def encode(self):
191 dlen = len(self.data)
192 checksum = self.computeChecksum()
193 return struct.pack(
194 _packetFormat,
195 self.sourcePseudoPort, self.destPseudoPort,
196 self.seqNum, self.ackNum, self.window,
197 self.flags, checksum, dlen) + self.data
198
199 def fragment(self, mtu):
200 if self.dlen < mtu:
201 return [self]
202 assert not self.syn, "should not be originating syn packets w/ data"
203 seqOfft = 0
204 L = []
205 # XXX TODO: need to take seqLaps into account, etc.
206 for chunk in iterchunks(self.data, mtu):
207 last = self.create(self.sourcePseudoPort,
208 self.destPseudoPort,
209 self.seqNum + seqOfft,
210 self.ackNum,
211 chunk,
212 self.window,
213 destination=self.destination,
214 ack=self.ack)
215 L.append(last)
216 seqOfft += len(chunk)
217 if self.fin:
218 last.fin = self.fin
219 last.checksum = last.computeChecksum()
220 return L
221
222
223def iterchunks(data, chunksize):
224 """iterate chunks of data
225 """
226 offt = 0
227 while offt < len(data):
228 yield data[offt:offt+chunksize]
229 offt += chunksize
230
231
232def ISN():
233 """
234 Initial Sequence Number generator.
235 """
236 # return int((time.time() * 1000000) / 4) % 2**32
237 return 0
238
239
240def segmentAcceptable(RCV_NXT, RCV_WND, SEG_SEQ, SEG_LEN):
241 # RFC page 26.
242 if SEG_LEN == 0 and RCV_WND == 0:
243 return SEG_SEQ == RCV_NXT
244 if SEG_LEN == 0 and RCV_WND > 0:
245 return ((RCV_NXT <= SEG_SEQ) and (SEG_SEQ < RCV_NXT + RCV_WND))
246 if SEG_LEN > 0 and RCV_WND == 0:
247 return False
248 if SEG_LEN > 0 and RCV_WND > 0:
249 return (( (RCV_NXT <= SEG_SEQ) and (SEG_SEQ < RCV_NXT + RCV_WND))
250 or ((RCV_NXT <= SEG_SEQ+SEG_LEN-1) and
251 (SEG_SEQ+SEG_LEN-1 < RCV_NXT + RCV_WND)))
252 assert 0, 'Should be impossible to get here.'
253 return False
254
255class BadPacketError(Exception):
256 """
257 A packet was bad for some reason.
258 """
259
260class ChecksumMismatchError(Exception):
261 """
262 The checksum and data received did not match.
263 """
264
265class TruncatedDataError(Exception):
266 """
267 The packet was truncated in transit, and all of the data did not arrive.
268 """
269
270class GarbageDataError(Exception):
271 """
272 Too much data was received (???)
273 """
274
275class PTCPConnection(tcpdfa.TCP):
276 """
277 Implementation of RFC 793 state machine.
278
279 @ivar oldestUnackedSendSeqNum: (TCP RFC: SND.UNA) The oldest (relative)
280 sequence number referring to an octet which we have sent or may send which
281 is unacknowledged. This begins at 0, which is special because it is not
282 for an octet, but rather for the initial SYN packet. Unless it is 0, this
283 represents the sequence number of self._outgoingBytes[0].
284
285 @ivar nextSendSeqNum: (TCP RFC: SND.NXT) The next (relative) sequence
286 number that we will send to our peer after the current buffered segments
287 have all been acknowledged. This is the sequence number of the
288 not-yet-extant octet in the stream at
289 self._outgoingBytes[len(self._outgoingBytes)].
290
291 @ivar nextRecvSeqNum: (TCP RFC: RCV.NXT) The next (relative) sequence
292 number that the peer should send to us if they want to send more data;
293 their first unacknowledged sequence number as far as we are concerned; the
294 left or lower edge of the receive window; the sequence number of the first
295 octet that has not been delivered to the application. changed whenever we
296 receive an appropriate ACK.
297
298 @ivar peerSendISN: the initial sequence number that the peer sent us during
299 the negotiation phase. All peer-relative sequence numbers are computed
300 using this. (see C{relativeSequence}).
301
302 @ivar hostSendISN: the initial sequence number that the we sent during the
303 negotiation phase. All host-relative sequence numbers are computed using
304 this. (see C{relativeSequence})
305
306 @ivar retransmissionQueue: a list of packets to be re-sent until their
307 acknowledgements come through.
308
309 @ivar recvWindow: (TCP RFC: RCV.WND) - the size [in octets] of the current
310 window allowed by this host, to be in transit from the other host.
311
312 @ivar sendWindow: (TCP RFC: SND.WND) - the size [in octets] of the current
313 window allowed by our peer, to be in transit from us.
314
315 """
316
317 mtu = 512 - _fixedSize
318
319 recvWindow = mtu
320 sendWindow = mtu
321 sendWindowRemaining = mtu * 2
322
323 protocol = None
324
325 def __init__(self,
326 hostPseudoPort, peerPseudoPort,
327 ptcp, factory, peerAddressTuple):
328 tcpdfa.TCP.__init__(self)
329 self.hostPseudoPort = hostPseudoPort
330 self.peerPseudoPort = peerPseudoPort
331 self.ptcp = ptcp
332 self.factory = factory
333 self._receiveBuffer = []
334 self.retransmissionQueue = []
335 self.peerAddressTuple = peerAddressTuple
336
337 self.oldestUnackedSendSeqNum = 0
338 self.nextSendSeqNum = 0
339 self.hostSendISN = 0
340 self.nextRecvSeqNum = 0
341 self.peerSendISN = 0
342 self.setPeerISN = False
343
344 peerSendISN = None
345
346 def packetReceived(self, packet):
347 # XXX TODO: probably have to do something to the packet here to
348 # identify its relative sequence number.
349
350 # print 'received', self, packet
351
352 if packet.stb:
353 # Shrink the MTU
354 [self.mtu] = struct.unpack('!H', packet.data)
355 rq = []
356 for pkt in self.retransmissionQueue:
357 rq.extend(pkt.fragment(self.mtu))
358 self.retransmissionQueue = rq
359 return
360
361 if self._paused:
362 return
363
364 generatedStateMachineInput = False
365 if packet.syn:
366 if packet.dlen:
367 # Whoops, what? SYNs probably can contain data, I think, but I
368 # certainly don't see anything in the spec about how to deal
369 # with this or in ethereal for how linux deals with it -glyph
370 raise BadPacketError(
371 "currently no data allowed in SYN packets: %r"
372 % (packet,))
373 else:
374 assert packet.segmentLength() == 1
375 if self.peerAddressTuple is None:
376 # we're a server
377 assert self.wasEverListen, "Clients must specify a connect address."
378 self.peerAddressTuple = packet.peerAddressTuple
379 else:
380 # we're a client
381 assert self.peerAddressTuple == packet.peerAddressTuple
382 if self.setPeerISN:
383 if self.peerSendISN != packet.seqNum:
384 raise BadPacketError(
385 "Peer ISN was already set to %s but incoming packet "
386 "tried to set it to %s" % (
387 self.peerSendISN, packet.seqNum))
388 if not self.retransmissionQueue:
389 # If our retransmissionQueue is hot, we are going to send
390 # them an ACK to this with the next packet we send them
391 # anyway; as a bonus, this will properly determine whether
392 # we're sending a SYN+ACK or merely an ACK; the only time
393 # we send an ACK is when we have nothing to say to them and
394 # they're blocked on getting a response to their SYN+ACK
395 # from us. -glyph
396 self.originate(ack=True)
397 return
398 self.setPeerISN = True
399 self.peerSendISN = packet.seqNum
400 # syn, fin, and data are mutually exclusive, so this relative
401 # sequence-number increment is done both here, and below in the
402 # data/fin processing block.
403 self.nextRecvSeqNum += packet.segmentLength()
404 if not packet.ack:
405 generatedStateMachineInput = True
406 self.input(tcpdfa.SYN)
407
408 SEG_ACK = packet.relativeAck() # aliasing this for easier reading w/
409 # the RFC
410 if packet.ack:
411 if (self.oldestUnackedSendSeqNum < SEG_ACK and
412 SEG_ACK <= self.nextSendSeqNum):
413 # According to the spec, an 'acceptable ack
414 rq = self.retransmissionQueue
415 while rq:
416 segmentOnQueue = rq[0]
417 qSegSeq = segmentOnQueue.relativeSeq()
418 if qSegSeq + segmentOnQueue.segmentLength() <= SEG_ACK:
419 # fully acknowledged, as per RFC!
420 rq.pop(0)
421 sminput = None
422 self.sendWindowRemaining += segmentOnQueue.segmentLength()
423 # print 'inc send window', self, self.sendWindowRemaining
424 if segmentOnQueue.syn:
425 if packet.syn:
426 sminput = tcpdfa.SYN_ACK
427 else:
428 sminput = tcpdfa.ACK
429 elif segmentOnQueue.fin:
430 sminput = tcpdfa.ACK
431 if sminput is not None:
432 # print 'ack input:', segmentOnQueue, packet, sminput
433 generatedStateMachineInput = True
434 self.input(sminput)
435 else:
436 break
437 else:
438 # write buffer is empty; alert the application layer.
439 self._writeBufferEmpty()
440 self.oldestUnackedSendSeqNum = SEG_ACK
441
442 if packet.syn:
443 assert generatedStateMachineInput
444 return
445
446 # XXX TODO: examine 'window' field and adjust sendWindowRemaining
447 # is it 'occupying a portion of valid receive sequence space'? I think
448 # this means 'packet which might acceptably contain useful data'
449 if not packet.segmentLength():
450 assert packet.ack, "What the _HELL_ is wrong with this packet:" +str(packet)
451 return
452
453 if not segmentAcceptable(self.nextRecvSeqNum,
454 self.recvWindow,
455 packet.relativeSeq(),
456 packet.segmentLength()):
457 # We have to transmit an ack here since it's old data. We probably
458 # need to ack in more states than just ESTABLISHED... but which
459 # ones?
460 if not self.retransmissionQueue:
461 self.originate(ack=True)
462 return
463
464 # OK! It's acceptable! Let's process the various bits of data.
465 # Where is the useful data in the packet?
466 if packet.relativeSeq() > self.nextRecvSeqNum:
467 # XXX: Here's what's going on. Data can be 'in the window', but
468 # still in the future. For example, if I have a window of length 3
469 # and I send segments DATA1(len 1) DATA2(len 1) FIN and you receive
470 # them in the order FIN DATA1 DATA2, you don't actually want to
471 # process the FIN until you've processed the data.
472
473 # For the moment we are just dropping anything that isn't exactly
474 # the next thing we want to process. This is perfectly valid;
475 # these packets might have been dropped, so the other end will have
476 # to retransmit them anyway.
477 return
478
479 if packet.dlen:
480 assert not packet.syn, 'no seriously I _do not_ know how to handle this'
481 usefulData = packet.data[self.nextRecvSeqNum - packet.relativeSeq():]
482 # DONT check/slice the window size here, the acceptability code
483 # checked it, we can over-ack if the other side is buggy (???)
484 if self.protocol is not None:
485 try:
486 self.protocol.dataReceived(usefulData)
487 except:
488 log.err()
489 self.loseConnection()
490
491 self.nextRecvSeqNum += packet.segmentLength()
492 if self.state == tcpdfa.ESTABLISHED:
493 # In all other states, the state machine takes care of sending ACKs
494 # in its output process.
495 self.originate(ack=True)
496
497 if packet.fin:
498 self.input(tcpdfa.FIN)
499
500
501 def getHost(self):
502 tupl = self.ptcp.transport.getHost()
503 return PTCPAddress((tupl.host, tupl.port),
504 self.pseudoPortPair)
505
506 def getPeer(self):
507 return PTCPAddress(self.peerAddressTuple,
508 self.pseudoPortPair)
509
510 _outgoingBytes = ''
511 _nagle = None
512
513 def write(self, bytes):
514 assert not self.disconnected, 'Writing to a transport that was already disconnected.'
515 self._outgoingBytes += bytes
516 self._writeLater()
517
518
519 def writeSequence(self, seq):
520 self.write(''.join(seq))
521
522
523 def _writeLater(self):
524 if self._nagle is None:
525 self._nagle = reactor.callLater(0.001, self._reallyWrite)
526
527 def _originateOneData(self):
528 amount = min(self.sendWindowRemaining, self.mtu)
529 sendOut = self._outgoingBytes[:amount]
530 # print 'originating data packet', len(sendOut)
531 self._outgoingBytes = self._outgoingBytes[amount:]
532 self.sendWindowRemaining -= len(sendOut)
533 self.originate(ack=True, data=sendOut)
534
535 def _reallyWrite(self):
536 # print self, 'really writing', self._paused
537 self._nagle = None
538 if self._outgoingBytes:
539 # print 'window and bytes', self.sendWindowRemaining, len(self._outgoingBytes)
540 while self.sendWindowRemaining and self._outgoingBytes:
541 self._originateOneData()
542
543 _retransmitter = None
544 _retransmitTimeout = 0.5
545
546 def _retransmitLater(self):
547 assert self.state != tcpdfa.CLOSED
548 if self._retransmitter is None:
549 self._retransmitter = reactor.callLater(self._retransmitTimeout, self._reallyRetransmit)
550
551 def _stopRetransmitting(self):
552 # used both as a quick-and-dirty test shutdown hack and a way to shut
553 # down when we die...
554 if self._retransmitter is not None:
555 self._retransmitter.cancel()
556 self._retransmitter = None
557 if self._nagle is not None:
558 self._nagle.cancel()
559 self._nagle = None
560 if self._closeWaitLoseConnection is not None:
561 self._closeWaitLoseConnection.cancel()
562 self._closeWaitLoseConnection = None
563
564 def _reallyRetransmit(self):
565 # XXX TODO: packet fragmentation & coalescing.
566 # print 'Wee a retransmit! What I got?', self.retransmissionQueue
567 self._retransmitter = None
568 if self.retransmissionQueue:
569 for packet in self.retransmissionQueue:
570 packet.retransmitCount -= 1
571 if packet.retransmitCount:
572 packet.ackNum = self.currentAckNum()
573 self.ptcp.sendPacket(packet)
574 else:
575 self.input(tcpdfa.TIMEOUT)
576 return
577 self._retransmitLater()
578
579 disconnecting = False # This is *TWISTED* level state-machine stuff,
580 # not TCP-level.
581
582 def loseConnection(self):
583 if not self.disconnecting:
584 self.disconnecting = True
585 if not self._outgoingBytes:
586 self._writeBufferEmpty()
587
588
589 def _writeBufferEmpty(self):
590 if self._outgoingBytes:
591 self._reallyWrite()
592 elif self.producer is not None:
593 if (not self.streamingProducer) or self.producerPaused:
594 self.producerPaused = False
595 self.producer.resumeProducing()
596 elif self.disconnecting and (not self.disconnected
597 or self.state == tcpdfa.CLOSE_WAIT):
598 self.input(tcpdfa.APP_CLOSE)
599
600
601 def _writeBufferFull(self):
602 # print 'my write buffer is full'
603 if (self.producer is not None
604 and not self.producerPaused):
605 self.producerPaused = True
606 # print 'producer pausing'
607 self.producer.pauseProducing()
608 # print 'producer paused'
609 else:
610 # print 'but I am not telling my producer to pause!'
611 # print ' ', self.producer, self.streamingProducer, self.producerPaused
612 pass
613
614
615 disconnected = False
616 producer = None
617 producerPaused = False
618 streamingProducer = False
619
620 def registerProducer(self, producer, streaming):
621 if self.producer is not None:
622 raise RuntimeError(
623 "Cannot register producer %s, "
624 "because producer %s was never unregistered."
625 % (producer, self.producer))
626 if self.disconnected:
627 producer.stopProducing()
628 else:
629 self.producer = producer
630 self.streamingProducer = streaming
631 if not streaming and not self._outgoingBytes:
632 producer.resumeProducing()
633
634 def unregisterProducer(self):
635 self.producer = None
636 if not self._outgoingBytes:
637 self._writeBufferEmpty()
638
639 _paused = False
640 def pauseProducing(self):
641 self._paused = True
642
643 def resumeProducing(self):
644 self._paused = False
645
646 def currentAckNum(self):
647 return (self.nextRecvSeqNum + self.peerSendISN) % (2**32)
648
649 def originate(self, data='', syn=False, ack=False, fin=False):
650 if syn:
651 # We really should be randomizing the ISN but until we finish the
652 # implementations of the various bits of wraparound logic that were
653 # started with relativeSequence
654 assert self.nextSendSeqNum == 0
655 assert self.hostSendISN == 0
656 p = PTCPPacket.create(self.hostPseudoPort,
657 self.peerPseudoPort,
658 seqNum=(self.nextSendSeqNum + self.hostSendISN) % (2**32),
659 ackNum=self.currentAckNum(),
660 data=data,
661 window=self.recvWindow,
662 syn=syn, ack=ack, fin=fin,
663 destination=self.peerAddressTuple)
664 # do we want to enqueue this packet for retransmission?
665 sl = p.segmentLength()
666 self.nextSendSeqNum += sl
667
668 if p.mustRetransmit():
669 # print self, 'originating retransmittable packet', len(self.retransmissionQueue)
670 if self.retransmissionQueue:
671 if self.retransmissionQueue[-1].fin:
672 raise AssertionError("Sending %r after FIN??!" % (p,))
673 # print 'putting it on the queue'
674 self.retransmissionQueue.append(p)
675 # print 'and sending it later'
676 self._retransmitLater()
677 if not self.sendWindowRemaining: # len(self.retransmissionQueue) > 5:
678 # print 'oh no my queue is too big'
679 # This is a random number (5) because I ought to be summing the
680 # packet lengths or something.
681 self._writeBufferFull()
682 else:
683 # print 'my queue is still small enough', len(self.retransmissionQueue), self, self.sendWindowRemaining
684 pass
685 self.ptcp.sendPacket(p)
686
687 # State machine transition definitions, hooray.
688 def transition_SYN_SENT_to_CLOSED(self):
689 """
690 The connection never got anywhere. Goodbye.
691 """
692 # XXX CONNECTOR API OMFG
693 self.factory.clientConnectionFailed(None, error.TimeoutError())
694
695
696 wasEverListen = False
697
698 def enter_LISTEN(self):
699 # Spec says this is necessary for RST handling; we need it for making
700 # sure it's OK to bind port numbers.
701 self.wasEverListen = True
702
703 def enter_CLOSED(self):
704 self.ptcp.connectionClosed(self)
705 self._stopRetransmitting()
706 if self._timeWaitCall is not None:
707 self._timeWaitCall.cancel()
708 self._timeWaitCall = None
709
710 _timeWaitCall = None
711 _timeWaitTimeout = 0.01 # REALLY fast timeout, right now this is for
712 # the tests...
713
714 def enter_TIME_WAIT(self):
715 self._stopRetransmitting()
716 self._timeWaitCall = reactor.callLater(self._timeWaitTimeout, self._do2mslTimeout)
717
718 def _do2mslTimeout(self):
719 self._timeWaitCall = None
720 self.input(tcpdfa.TIMEOUT)
721
722 peerAddressTuple = None
723
724 def transition_LISTEN_to_SYN_SENT(self):
725 """
726 Uh, what? We were listening and we tried to send some bytes.
727 This is an error for PTCP.
728 """
729 raise StateError("You can't write anything until someone connects to you.")
730
731# def invalidInput(self, datum):
732# print self, self.protocol, 'invalid input', datum
733
734 def pseudoPortPair():
735 def get(self):
736 return (self.hostPseudoPort,
737 self.peerPseudoPort)
738 return get,
739 pseudoPortPair = property(*pseudoPortPair())
740
741 def enter_ESTABLISHED(self):
742 """
743 We sent out SYN, they acknowledged it. Congratulations, you
744 have a new baby connection.
745 """
746 assert not self.disconnecting
747 assert not self.disconnected
748 try:
749 p = self.factory.buildProtocol(PTCPAddress(
750 self.peerAddressTuple, self.pseudoPortPair))
751 p.makeConnection(self)
752 except:
753 log.msg("Exception during PTCP connection setup.")
754 log.err()
755 self.loseConnection()
756 else:
757 self.protocol = p
758
759 def exit_ESTABLISHED(self):
760 assert not self.disconnected
761 self.disconnected = True
762 try:
763 self.protocol.connectionLost(Failure(CONNECTION_DONE))
764 except:
765 log.err()
766 self.protocol = None
767
768 if self.producer is not None:
769 try:
770 self.producer.stopProducing()
771 except:
772 log.err()
773 self.producer = None
774
775
776 _closeWaitLoseConnection = None
777
778 def enter_CLOSE_WAIT(self):
779 # Twisted automatically reacts to network half-close by issuing a full
780 # close.
781 self._closeWaitLoseConnection = reactor.callLater(0.01, self._loseConnectionBecauseOfCloseWait)
782
783 def _loseConnectionBecauseOfCloseWait(self):
784 self._closeWaitLoseConnection = None
785 self.loseConnection()
786
787 def immediateShutdown(self):
788 """_IMMEDIATELY_ shut down this connection, sending one (non-retransmitted)
789 app-close packet, emptying our buffers, clearing our producer and
790 getting ready to die right after this call.
791 """
792 self._outgoingBytes = ''
793 if self.state == tcpdfa.ESTABLISHED:
794 self.input(tcpdfa.APP_CLOSE)
795 self._stopRetransmitting()
796 self._reallyRetransmit()
797
798 # All states that we can reasonably be in handle a timeout; force our
799 # connection to think that it's become desynchronized with the other
800 # end so that it will totally shut itself down.
801
802 self.input(tcpdfa.TIMEOUT)
803 assert self._retransmitter is None
804 assert self._nagle is None
805
806 def output_ACK(self):
807 self.originate(ack=True)
808
809 def output_FIN(self):
810 self.originate(fin=True)
811
812 def output_SYN_ACK(self):
813 self.originate(syn=True, ack=True)
814
815 def output_SYN(self):
816 self.originate(syn=True)
817
818class PTCPAddress(object):
819 # garbage
820
821 def __init__(self, (host, port), (pseudoHostPort, pseudoPeerPort)):
822 self.host = host
823 self.port = port
824 self.pseudoHostPort = pseudoHostPort
825 self.pseudoPeerPort = pseudoPeerPort
826
827 def __repr__(self):
828 return 'PTCPAddress((%r, %r), (%r, %r))' % (
829 self.host, self.port,
830 self.pseudoHostPort,
831 self.pseudoPeerPort)
832
833
834
835class _PendingEvent(object):
836 def __init__(self):
837 self.listeners = []
838
839
840 def deferred(self):
841 d = Deferred()
842 self.listeners.append(d)
843 return d
844
845
846 def callback(self, result):
847 l = self.listeners
848 self.listeners = []
849 for d in l:
850 d.callback(result)
851
852
853 def errback(self, result=None):
854 if result is None:
855 result = Failure()
856 l = self.listeners
857 self.listeners = []
858 for d in l:
859 d.errback(result)
860
861
862
863class PTCP(protocol.DatagramProtocol):
864 """
865 L{PTCP} implements a strongly TCP-like protocol on top of UDP. It
866 provides a transport which is connection-oriented, streaming,
867 ordered, and reliable.
868
869 @ivar factory: A L{ServerFactory} which is used to create
870 L{IProtocol} providers whenever a new PTCP connection is made
871 to this port.
872
873 @ivar _connections: A mapping of endpoint addresses to connection
874 objects. These are the active connections being multiplexed
875 over this UDP port. Many PTCP connections may run over the
876 same L{PTCP} instance, communicating with many different
877 remote hosts as well as multiplexing different PTCP
878 connections to the same remote host. The mapping keys,
879 endpoint addresses, are three-tuples of:
880
881 - The destination pseudo-port which is always C{1}
882 - The source pseudo-port
883 - A (host, port) tuple giving the UDP address of a PTCP
884 peer holding the other side of the connection
885
886 The mapping values, connection objects, are L{PTCPConnection}
887 instances.
888 @type _connections: C{dict}
889
890 """
891 # External API
892
893 def __init__(self, factory):
894 self.factory = factory
895 self._allConnectionsClosed = _PendingEvent()
896
897
898 def connect(self, factory, host, port, pseudoPort=1):
899 """
900 Attempt to establish a new connection via PTCP to the given
901 remote address.
902
903 @param factory: A L{ClientFactory} which will be used to
904 create an L{IProtocol} provider if the connection is
905 successfully set up, or which will have failure callbacks
906 invoked on it otherwise.
907
908 @param host: The IP address of another listening PTCP port to
909 connect to.
910 @type host: C{str}
911
912 @param port: The port number of that other listening PTCP port
913 to connect to.
914 @type port: C{int}
915
916 @param pseudoPort: Not really implemented. Do not pass a
917 value for this parameter or things will break.
918
919 @return: A L{PTCPConnection} instance representing the new
920 connection, but you really shouldn't use this for
921 anything. Write a protocol!
922 """
923 sourcePseudoPort = genConnID() % MAX_PSEUDO_PORT
924 conn = self._connections[(pseudoPort, sourcePseudoPort, (host, port))
925 ] = PTCPConnection(
926 sourcePseudoPort, pseudoPort, self, factory, (host, port))
927 conn.input(tcpdfa.APP_ACTIVE_OPEN)
928 return conn
929
930 def sendPacket(self, packet):
931 if self.transportGoneAway:
932 return
933 self.transport.write(packet.encode(), packet.destination)
934
935
936 # Internal stuff
937 def startProtocol(self):
938 self.transportGoneAway = False
939 self._lastConnID = 10 # random.randrange(2 ** 32)
940 self._connections = {}
941
942 def _finalCleanup(self):
943 """
944 Clean up all of our connections by issuing application-level close and
945 stop notifications, sending hail-mary final FIN packets (which may not
946 reach the other end, but nevertheless can be useful) when possible.
947 """
948 for conn in self._connections.values():
949 conn.immediateShutdown()
950 assert not self._connections
951
952 def stopProtocol(self):
953 """
954 Notification from twisted that our underlying port has gone away;
955 make sure we're not going to try to send any packets through our
956 transport and blow up, then shut down all of our protocols, issuing
957 appr
958 opriate application-level messages.
959 """
960 self.transportGoneAway = True
961 self._finalCleanup()
962
963 def cleanupAndClose(self):
964 """
965 Clean up all remaining connections, then close our transport.
966
967 Although in a pinch we will do cleanup after our socket has gone away
968 (if it does so unexpectedly, above in stopProtocol), we would really
969 prefer to do cleanup while we still have access to a transport, since
970 that way we can force out a few final packets and save the remote
971 application an awkward timeout (if it happens to get through, which
972 is generally likely).
973 """
974 self._finalCleanup()
975 return self._stop()
976
977 def datagramReceived(self, bytes, addr):
978 if len(bytes) < _fixedSize:
979 # It can't be any good.
980 return
981
982 pkt = PTCPPacket.decode(bytes, addr)
983 try:
984 pkt.verifyChecksum()
985 except TruncatedDataError:
986# print '(ptcp packet truncated: %r)' % (pkt,)
987 self.sendPacket(
988 PTCPPacket.create(
989 pkt.destPseudoPort,
990 pkt.sourcePseudoPort,
991 0,
992 0,
993 struct.pack('!H', len(pkt.data)),
994 stb=True,
995 destination=addr))
996 except GarbageDataError:
997 print "garbage data!", pkt
998 except ChecksumMismatchError, cme:
999 print "bad checksum", pkt, cme
1000 print repr(pkt.data)
1001 print hex(pkt.checksum), hex(pkt.computeChecksum())
1002 else:
1003 self.packetReceived(pkt)
1004
1005 stopped = False
1006 def _stop(self, result=None):
1007 if not self.stopped:
1008 self.stopped = True
1009 return self.transport.stopListening()
1010 else:
1011 return defer.succeed(None)
1012
1013 def waitForAllConnectionsToClose(self):
1014 """
1015 Wait for all currently-open connections to enter the 'CLOSED' state.
1016 Currently this is only usable from test fixtures.
1017 """
1018 if not self._connections:
1019 return self._stop()
1020 return self._allConnectionsClosed.deferred().addBoth(self._stop)
1021
1022 def connectionClosed(self, ptcpConn):
1023 packey = (ptcpConn.peerPseudoPort, ptcpConn.hostPseudoPort,
1024 ptcpConn.peerAddressTuple)
1025 del self._connections[packey]
1026 if ((not self.transportGoneAway) and
1027 (not self._connections) and
1028 self.factory is None):
1029 self._stop()
1030 if not self._connections:
1031 self._allConnectionsClosed.callback(None)
1032
1033 def packetReceived(self, packet):
1034 packey = (packet.sourcePseudoPort, packet.destPseudoPort, packet.peerAddressTuple)
1035 if packey not in self._connections:
1036 if packet.flags == _SYN and packet.destPseudoPort == 1: # SYN and _ONLY_ SYN set.
1037 conn = PTCPConnection(packet.destPseudoPort,
1038 packet.sourcePseudoPort, self,
1039 self.factory, packet.peerAddressTuple)
1040 conn.input(tcpdfa.APP_PASSIVE_OPEN)
1041 self._connections[packey] = conn
1042 else:
1043 log.msg("corrupted packet? %r %r %r" % (packet,packey, self._connections))
1044 return
1045 try:
1046 self._connections[packey].packetReceived(packet)
1047 except:
1048 log.msg("PTCPConnection error on %r:" % (packet,))
1049 log.err()
1050 del self._connections[packey]
10510
=== removed file 'Vertex/vertex/q2q.py'
--- Vertex/vertex/q2q.py 2012-03-14 23:42:53 +0000
+++ Vertex/vertex/q2q.py 1970-01-01 00:00:00 +0000
@@ -1,2763 +0,0 @@
1# -*- test-case-name: vertex.test.test_q2q -*-
2# Copyright 2005-2008 Divmod, Inc. See LICENSE file for details
3
4"""
5I{Quotient to Quotient} protocol implementation.
6"""
7
8# stdlib
9import itertools
10from hashlib import md5
11import struct
12import datetime
13import time
14from collections import namedtuple
15
16from pprint import pformat
17
18from zope.interface import implements
19
20# twisted
21from twisted.internet import reactor, defer, interfaces, protocol, error
22from twisted.internet.main import CONNECTION_DONE
23from twisted.internet.ssl import (
24 CertificateRequest, Certificate, PrivateCertificate, KeyPair,
25 DistinguishedName)
26from twisted.python import log
27from twisted.python.failure import Failure
28from twisted.application import service
29
30# twisted.cred
31from twisted.cred.checkers import ICredentialsChecker
32from twisted.cred.portal import IRealm, Portal
33from twisted.cred.credentials import IUsernamePassword, UsernamePassword
34from twisted.cred.error import UnauthorizedLogin
35
36from twisted.protocols.amp import Argument, Boolean, Integer, String, Unicode, ListOf, AmpList
37from twisted.protocols.amp import AmpBox, Command, StartTLS, ProtocolSwitchCommand, AMP
38from twisted.protocols.amp import _objectsToStrings
39
40# vertex
41from vertex import subproducer, ptcp
42from vertex import endpoint, ivertex
43from vertex.conncache import ConnectionCache
44
45MESSAGE_PROTOCOL = 'q2q-message'
46port = 8788
47
48class ConnectionError(Exception):
49 pass
50
51class AttemptsFailed(ConnectionError):
52 pass
53
54class NoAttemptsMade(ConnectionError):
55 pass
56
57class VerifyError(Exception):
58 pass
59
60class BadCertificateRequest(VerifyError):
61 pass
62
63class IgnoreConnectionFailed(protocol.ClientFactory):
64 def __init__(self, realFactory):
65 self.realFactory = realFactory
66
67 def clientConnectionLost(self, connector, reason):
68 self.realFactory.clientConnectionLost(connector, reason)
69
70 def clientConnectionFailed(self, connector, reason):
71 pass
72
73 def buildProtocol(self, addr):
74 return self.realFactory.buildProtocol(addr)
75
76class Q2QAddress(object):
77 def __init__(self, domain, resource=None):
78 self.resource = resource
79 self.domain = domain
80
81 def domainAddress(self):
82 """ Return an Address object which is the same as this one with ONLY the
83 'domain' attribute set, not 'resource'.
84
85 May return 'self' if 'resource' is already None.
86 """
87 if self.resource is None:
88 return self
89 else:
90 return Q2QAddress(self.domain)
91
92 def claimedAsIssuerOf(self, cert):
93 """
94 Check if the information in a provided certificate *CLAIMS* to be issued by
95 this address.
96
97 PLEASE NOTE THAT THIS METHOD IS IN NO WAY AUTHORITATIVE. It does not
98 perform any cryptographic checks.
99
100 Currently this check is if L{Q2QAddress.__str__}C{(self)} is equivalent
101 to the commonName on the certificate's issuer.
102 """
103 return cert.getIssuer().commonName == str(self)
104
105 def claimedAsSubjectOf(self, cert):
106 """
107 Check if the information in a provided certificate *CLAIMS* to be
108 provided for use by this address.
109
110 PLEASE NOTE THAT THIS METHOD IS IN NO WAY AUTHORITATIVE. It does not
111 perform any cryptographic checks.
112
113 Currently this check is if L{Q2QAddress.__str__}C{(self)} is equivalent
114 to the commonName on the certificate's subject.
115 """
116 return cert.getSubject().commonName == str(self)
117
118 def __cmp__(self, other):
119 if not isinstance(other, Q2QAddress):
120 return cmp(self.__class__, other.__class__)
121 return cmp((self.domain, self.resource), (other.domain, other.resource))
122
123 def __iter__(self):
124 return iter((self.resource, self.domain))
125
126 def __str__(self):
127 """
128 Return a string of the normalized form of this address. e.g.::
129
130 glyph@divmod.com # for a user
131 divmod.com # for a domain
132 """
133 if self.resource:
134 resource = self.resource + '@'
135 else:
136 resource = ''
137 return (resource + self.domain).encode('utf-8')
138
139 def __repr__(self):
140 return '<Q2Q at %s>' % self.__str__()
141
142 def __hash__(self):
143 return hash(str(self))
144
145 def fromString(cls, string):
146 args = string.split("@",1)
147 args.reverse()
148 return cls(*args)
149 fromString = classmethod(fromString)
150
151
152class VirtualTransportAddress:
153 def __init__(self, underlying):
154 self.underlying = underlying
155
156 def __repr__(self):
157 return 'VirtualTransportAddress(%r)' % (self.underlying,)
158
159class Q2QTransportAddress:
160 """
161 The return value of getPeer() and getHost() for Q2Q-enabled transports.
162 Passed to buildProtocol of factories passed to listenQ2Q.
163
164 @ivar underlying: The return value of the underlying transport's getPeer()
165 or getHost(); an address which indicates the path which the bytes carrying
166 Q2Q traffic are travelling over. It is tempting to think of this as a
167 'physical' layer but that it not necessarily accurate; there are
168 potentially multiple layers of wrapping on any Q2Q transport, as an SSL
169 transport may be tunnelled over a UDP NAT-traversal layer. Implements
170 C{IAddress} from Twisted, for all the good that will do you.
171
172 @ivar logical: a L{Q2QAddress}, The logical peer; the user ostensibly
173 listening to data on the other end of this transport.
174
175 @ivar protocol: a L{str}, the name of the protocol that is connected.
176 """
177
178 def __init__(self, underlying, logical, protocol):
179 self.underlying = underlying
180 self.logical = logical
181 self.protocol = protocol
182
183 def __repr__(self):
184 return 'Q2QTransportAddress(%r, %r, %r)' % (
185 self.underlying,
186 self.logical,
187 self.protocol)
188
189
190class AmpTime(Argument):
191 def toString(self, inObject):
192 return inObject.strftime("%Y-%m-%dT%H:%M:%S")
193
194
195 def fromString(self, inString):
196 return datetime.datetime.strptime(inString, "%Y-%m-%dT%H:%M:%S")
197
198
199
200class Q2QAddressArgument(Argument):
201 fromString = Q2QAddress.fromString
202 toString = Q2QAddress.__str__
203
204class HostPort(Argument):
205 def toString(self, inObj):
206 return "%s:%d" % tuple(inObj)
207
208 def fromString(self, inStr):
209 host, sPort = inStr.split(":")
210 return (host, int(sPort))
211
212
213
214class _BinaryLoadable(String):
215 def toString(self, arg):
216 assert isinstance(arg, self.loader), "%r not %r" % (arg, self.loader)
217 return String.toString(self, arg.dump())
218
219 def fromString(self, arg):
220 return self.loader.load(String.fromString(self, arg))
221
222class CertReq(_BinaryLoadable):
223 loader = CertificateRequest
224
225class Cert(_BinaryLoadable):
226 loader = Certificate
227
228from twisted.internet import protocol
229
230class Q2QClientProtocolFactoryWrapper:
231
232 def __init__(self, service, cpf, fromAddress, toAddress, protocolName,
233 connectionEstablishedDeferred):
234 self.service = service
235 self.cpf = cpf
236 self.fromAddress = fromAddress
237 self.toAddress = toAddress
238 self.protocolName = protocolName
239 self.connectionEstablishedDeferred = connectionEstablishedDeferred
240 connectionEstablishedDeferred.addCallback(self.setMyClient)
241
242 myClient = None
243 def setMyClient(self, myClient):
244 # print '***CLIENT SET***', self, self.fromAddress, self.toAddress, self.cpf
245 self.myClient = myClient
246 return myClient
247
248 def buildProtocol(self, addr):
249 # xxx modify addr to include q2q information.
250 subProto = self.cpf.buildProtocol(self.toAddress)
251 myProto = SeparateConnectionTransport(self.service, subProto, self.fromAddress,
252 self.toAddress, self.protocolName,
253 self.connectionEstablishedDeferred)
254 return myProto
255
256 def clientConnectionFailed(self, connector, reason):
257 # DON'T forward this to our client protocol factory; only one attempt
258 # has failed; let that happen later, when _ALL_ attempts have failed.
259 assert self.myClient is None
260 self.connectionEstablishedDeferred.errback(reason)
261
262 def clientConnectionLost(self, connector, reason):
263 # as in clientConnectionFailed, don't bother to forward; this
264 # clientConnectionLost is actually a clientConnectionFailed for the
265 # underlying transport.
266 if self.myClient is not None:
267 # forward in this case because it's likely that we need to pass it
268 # along...
269 self.cpf.clientConnectionLost(connector, reason)
270
271 def doStart(self):
272 self.cpf.doStart()
273
274 def doStop(self):
275 self.cpf.doStop()
276
277class ImmediatelyLoseConnection(protocol.Protocol):
278 def connectionMade(self):
279 self.transport.loseConnection()
280
281class AbstractConnectionAttempt(protocol.ClientFactory):
282
283
284 def __init__(self, method, q2qproto, connectionID, fromAddress, toAddress,
285 protocolName, clientProtocolFactory, issueGreeting=False):
286 self.method = method
287 self.q2qproto = q2qproto
288 assert isinstance(connectionID, str)
289 self.connectionID = connectionID
290 self.q2qproto = q2qproto
291 self.fromAddress = fromAddress
292 self.toAddress = toAddress
293 self.protocolName = protocolName
294 self.deferred = defer.Deferred()
295 self.clientProtocolFactory = Q2QClientProtocolFactoryWrapper(
296 q2qproto.service,
297 clientProtocolFactory, fromAddress, toAddress, protocolName,
298 self.deferred)
299 self.issueGreeting = issueGreeting
300
301
302 def startAttempt(self):
303 """
304 +-+
305 |?|
306 +-+
307 """
308 raise NotImplementedError()
309
310
311 q2qb = None
312
313 cancelled = False
314
315 def buildProtocol(self, addr):
316 if self.cancelled:
317 return ImmediatelyLoseConnection()
318 assert self.q2qb is None
319 self.q2qb = Q2QBootstrap(
320 self.connectionID, self.clientProtocolFactory)
321 return self.q2qb
322
323 def clientConnectionFailed(self, connector, reason):
324 """
325 """
326 # Don't bother forwarding. In fact this should probably never be
327 # called because we're not bothering to forward them along from
328 # Q2QClientProtocolFactoryWrapper
329
330 def clientConnectionLost(self, connector, reason):
331 """
332 """
333 # we don't care - this will be handled by Q2QBootstrap.
334
335 def cancel(self):
336 """
337 - Stop attempting to connect.
338
339 - If a connection is somehow made after this has been cancelled, reject
340 it.
341
342 - Clean up any resources, such as listening UDP or TCP ports,
343 associated with this connection attempt [obviously, that are unshared
344 by other connection attempt]
345
346 """
347 self.cancelled = True
348
349
350class TCPConnectionAttempt(AbstractConnectionAttempt):
351 attempted = False
352 def startAttempt(self):
353 assert not self.attempted
354 self.attempted = True
355 reactor.connectTCP(self.method.host, self.method.port, self)
356 return self.deferred
357
358
359class TCPMethod:
360 def __init__(self, hostport):
361 self.host, port = hostport.split(':')
362 self.port = int(port)
363
364 attemptFactory = TCPConnectionAttempt
365 relayable = True
366 ptype = 'tcp'
367
368 def toString(self):
369 return '%s@%s:%d' % (self.ptype, self.host, self.port)
370
371 def __repr__(self):
372 return '<%s>'%self.toString()
373
374 def attempt(self, *a):
375 return [self.attemptFactory(self, *a)]
376
377connectionCounter = itertools.count().next
378connectionCounter()
379
380class VirtualConnectionAttempt(AbstractConnectionAttempt):
381 attempted = False
382 def startAttempt(self):
383 assert not self.attempted
384 self.attempted = True
385 cid = connectionCounter()
386 if self.q2qproto.isServer:
387 cid = -cid
388 innerTransport = VirtualTransport(self.q2qproto, cid, self, True)
389 def startit(result):
390 proto = innerTransport.startProtocol()
391 return self.deferred
392
393 d = self.q2qproto.callRemote(Virtual, id=cid)
394 d.addCallback(startit)
395 return d
396
397
398class VirtualMethod:
399 def __init__(self, virt=None):
400 pass
401
402 relayable = False
403
404 def toString(self):
405 return 'virtual'
406
407 def __repr__(self):
408 return '<%s>' % (self.toString(),)
409
410 def attempt(self, *a):
411 return [VirtualConnectionAttempt(self, *a)]
412
413
414class _PTCPConnectionAttempt1NoPress(AbstractConnectionAttempt):
415 attempted = False
416 def startAttempt(self):
417 assert not self.attempted
418 self.attempted = True
419 svc = self.q2qproto.service
420 dsp = svc.dispatcher
421 dsp.connectPTCP(
422 self.method.host, self.method.port, self,
423 svc.sharedUDPPortnum)
424 return self.deferred
425
426class _PTCPConnectionAttemptPress(AbstractConnectionAttempt):
427 attempted = False
428 def startAttempt(self):
429 assert not self.attempted
430 self.attempted = True
431
432 svc = self.q2qproto.service
433 dsp = svc.dispatcher
434 newPort = self.newPort = dsp.bindNewPort()
435 dsp.connectPTCP(
436 self.method.host, self.method.port, self,
437 newPort)
438
439 return self.deferred
440
441 def cancel(self):
442 if not self.cancelled:
443 self.q2qproto.service.dispatcher.unbindPort(self.newPort)
444 else:
445 print 'totally wacky, [press] cancelled twice!'
446 AbstractConnectionAttempt.cancel(self)
447
448class PTCPMethod(TCPMethod):
449 """Pseudo-TCP method.
450 """
451 ptype = 'ptcp'
452
453 def attempt(self, *a):
454 return [_PTCPConnectionAttempt1NoPress(self, *a),
455 _PTCPConnectionAttemptPress(self, *a)]
456
457class RPTCPConnectionAttempt(AbstractConnectionAttempt):
458 attempted = False
459 def startAttempt(self):
460 assert not self.attempted
461 self.attempted = True
462
463 realLocalUDP = self.newPort = self.q2qproto.service.dispatcher.seedNAT((self.method.host, self.method.port))
464 # self.host and self.port are remote host and port
465 # realLocalUDP is a local port
466
467 # The arguments here are given from the perspective of the recipient of
468 # the command. we are asking the recipient of the connection to map a
469 # NAT entry of a pre-existing listening UDP socket on their end of the
470 # connection by sending us some traffic. therefore the src is their
471 # endpoint, the dst is our endpoint, the user we are asking them to
472 # send TO is us, the user we are asking them to accept this FROM is us.
473
474 # we include protocol as an arg because this is helpful for relaying.
475
476 def enbinden(boundereded):
477 if not self.cancelled:
478 self.q2qproto.service.dispatcher.connectPTCP(
479 self.method.host, self.method.port, self, realLocalUDP
480 )
481 return self.deferred
482
483 def swallowKnown(error):
484 error.trap(ConnectionError)
485 self.deferred.errback(CONNECTION_DONE)
486 return self.deferred
487
488 d = self.q2qproto.callRemote(
489 BindUDP,
490 q2qsrc=self.toAddress,
491 q2qdst=self.fromAddress,
492 protocol=self.protocolName,
493 udpsrc=(self.method.host, self.method.port),
494 udpdst=(self.q2qproto._determinePublicIP(), realLocalUDP))
495 d.addCallbacks(enbinden, swallowKnown)
496 return d
497
498 def cancel(self):
499 if not self.cancelled:
500 self.q2qproto.service.dispatcher.unbindPort(self.newPort)
501 else:
502 print 'totally wacky, [rptcp] cancelled twice!'
503 AbstractConnectionAttempt.cancel(self)
504
505
506
507
508class RPTCPMethod(TCPMethod):
509 """ Certain NATs respond very poorly to seed traffic: e.g. if they receive
510 unsolicited traffic to a particular port, they will make that outbound port
511 unavailable for outbound traffic originated internally. The
512 Reverse-Pseudo-TCP method is a way to have the *sender* send the first UDP
513 packet, so they will bind it.
514
515 This is a worst-case scenario: if both ends of the connection have NATs
516 which behave this way, there is no way to establish a connection.
517 """
518
519 ptype = 'rptcp'
520 attemptFactory = RPTCPConnectionAttempt
521
522
523class UnknownMethod:
524
525 relayable = True
526
527 def __init__(self, S):
528 self.string = S
529
530 def attemptConnect(self, q2qproto, connectionID, From, to,
531 protocolName, protocolFactory):
532 return defer.fail(Failure(ConnectionError(
533 "unknown connection method: %s" % (self.string,))))
534
535
536_methodFactories = {'virtual': VirtualMethod,
537 'tcp': TCPMethod,
538 'ptcp': PTCPMethod,
539 'rptcp': RPTCPMethod}
540
541class Method(Argument):
542 def toString(self, inObj):
543 return inObj.toString()
544
545
546 def fromString(self, inString):
547 f = inString.split("@", 1)
548 factoryName = f[0]
549 if len(f) > 1:
550 factoryData = f[1]
551 else:
552 factoryData = ''
553 methodFactory = _methodFactories.get(factoryName, None)
554 if methodFactory is None:
555 factory = UnknownMethod(inString)
556 else:
557 factory = methodFactory(factoryData)
558 return factory
559
560
561class Secure(StartTLS):
562
563 commandName = "secure"
564 arguments = StartTLS.arguments + [
565 ('From', Q2QAddressArgument(optional=True)),
566 ('to', Q2QAddressArgument()),
567 ('authorize', Boolean())
568 ]
569
570
571
572class Listen(Command):
573 """
574 A simple command for registering interest with an active Q2Q connection
575 to hear from a server when others come calling. An occurrence of this
576 command might have this appearance on the wire::
577
578 C: -Command: Listen
579 C: -Ask: 1
580 C: From: glyph@divmod.com
581 C: Protocols: q2q-example, q2q-example2
582 C: Description: some simple protocols
583 C:
584 S: -Answer: 1
585 S:
586
587 This puts some state on the server side that will affect any Connect
588 commands with q2q-example or q2q-example2 in the Protocol: header.
589 """
590
591 commandName = 'listen'
592 arguments = [
593 ('From', Q2QAddressArgument()),
594 ('protocols', ListOf(String())),
595 ('description', Unicode())]
596
597 result = []
598
599class ConnectionStartBox(AmpBox):
600 def __init__(self, __transport):
601 super(ConnectionStartBox, self).__init__()
602 self.virtualTransport = __transport
603
604 # XXX Overriding a private interface
605 def _sendTo(self, proto):
606 super(ConnectionStartBox, self)._sendTo(proto)
607 self.virtualTransport.startProtocol()
608
609class Virtual(Command):
610 commandName = 'virtual'
611 result = []
612
613 arguments = [('id', Integer())]
614
615 def makeResponse(cls, objects, proto):
616 tpt = objects.pop('__transport__')
617 # XXX Using a private API
618 return _objectsToStrings(
619 objects, cls.response,
620 ConnectionStartBox(tpt),
621 proto)
622
623 makeResponse = classmethod(makeResponse)
624
625class Identify(Command):
626 """
627 Respond to an IDENTIFY command with a self-signed certificate for the
628 domain requested, assuming we are an authority for said domain. An
629 occurrence of this command might have this appearance on the wire::
630
631 C: -Command: Identify
632 C: -Ask: 1
633 C: Domain: divmod.com
634 C:
635 S: -Answer: 1
636 S: Certificate: <<<base64-encoded self-signed certificate of divmod.com>>>
637 S:
638
639 """
640
641 commandName = 'identify'
642
643 arguments = [('subject', Q2QAddressArgument())]
644
645 response = [('certificate', Cert())]
646
647class BindUDP(Command):
648 """
649 See UDPXMethod
650 """
651
652 commandName = 'bind-udp'
653
654 arguments = [
655 ('protocol', String()),
656 ('q2qsrc', Q2QAddressArgument()),
657 ('q2qdst', Q2QAddressArgument()),
658 ('udpsrc', HostPort()),
659 ('udpdst', HostPort()),
660 ]
661
662 errors = {ConnectionError: 'ConnectionError'}
663
664 response = []
665
666class SourceIP(Command):
667 """
668 Ask a server on the public internet what my public IP probably is. An
669 occurrence of this command might have this appearance on the wire::
670
671 C: -Command: Source-IP
672 C: -Ask: 1
673 C:
674 S: -Answer: 1
675 S: IP: 4.3.2.1
676 S:
677
678 """
679
680 commandName = 'source-ip'
681
682 arguments = []
683
684 response = [('ip', String())]
685
686class Inbound(Command):
687 """
688 Request information about where to connect to a particular resource.
689
690 Generally speaking this is an "I want to connect to you" request.
691
692 The format of this request is transport neutral except for the optional
693 'Udp_Source' header, which specifies an IP/Port pair for all receiving peers to
694 send an almost-empty (suggested value of '\\r\\n') UDP packet to to help
695 with NAT traversal issues.
696
697 See L{Q2QService.connectQ2Q} for details.
698
699 An occurrence of this command might have this appearance on the wire::
700
701 C: -Command: Inbound
702 C: -Ask: 1
703 C: From: glyph@divmod.com
704 C: Id: 681949ffa3be@twistedmatrix.com
705 C: To: radix@twistedmatrix.com
706 C: Protocol: q2q-example
707 C: Udp_Source: 1.2.3.4:4321
708 C:
709 S: -Answer: 1
710 S: Listeners:
711 S: Description: at lab
712 S: Methods: tcp@18.38.12.4:3827, virtual
713 S:
714 S: Description: my home machine
715 S: Methods: tcp@187.48.38.3:49812, udp@187.48.38.3:49814, virtual
716
717 Now the connection-id has been registered and either client or server can
718 issue WRITE or CLOSE commands.
719
720 Failure modes::
721
722 - "NotFound": the toResource or toDomain is invalid, or the resource does
723 not speak that protocol.
724
725 - "VerifyError": Authenticity or security for the requested connection
726 could not be authorized. This is a fatal error: the connection will be
727 dropped.
728
729 The "Udp_Source" header indicates the address from which this Inbound chain
730 originated. It is to be used to establish connections where possible
731 between NATs which require traffic between two host/port pairs to be
732 bidirectional before a "hole" is established, such as port restricted cone
733 and symmetric NATs. (Note, this only has about a 30% probability of
734 working on a symmetric NAT, but it's worth trying sometimes anyway). Any
735 UDP-based connection methods (currently only Gin, but in principle others
736 such as RTP, RTCP, SIP and Quake traffic) that wish to use this connection
737 must first send some garbage traffic to the host/port specified by the
738 "Udp_Source" header.
739
740 The response is a list of "listeners" - a small (unicode) textual
741 description of a host, plus a list of methods describing how to connect to
742 it.
743 """
744
745 commandName = 'inbound'
746 arguments = [('From', Q2QAddressArgument()),
747 ('to', Q2QAddressArgument()),
748 ('protocol', String()),
749 ('udp_source', HostPort(optional=True))]
750
751 response = [('listeners', AmpList(
752 [('id', String()),
753 ('certificate', Cert(optional=True)),
754 ('methods', ListOf(Method())),
755 ('expires', AmpTime()),
756 ('description', Unicode())]))]
757
758 errors = {KeyError: "NotFound"}
759 fatalErrors = {VerifyError: "VerifyError"}
760
761class Outbound(Command):
762 """Similar to Inbound, but _requires that the recipient already has the
763 id parameter as an outgoing connection attempt_.
764 """
765 commandName = 'outbound'
766
767 arguments = [('From', Q2QAddressArgument()),
768 ('to', Q2QAddressArgument()),
769 ('protocol', String()),
770 ('id', String()),
771 ('methods', ListOf(Method()))]
772
773 response = []
774
775 errors = {AttemptsFailed: 'AttemptsFailed'}
776
777class Sign(Command):
778 commandName = 'sign'
779 arguments = [('certificate_request', CertReq()),
780 ('password', String())]
781
782 response = [('certificate', Cert())]
783
784 errors = {KeyError: "NoSuchUser",
785 BadCertificateRequest: "BadCertificateRequest"}
786
787class Choke(Command):
788 """Ask our peer to be quiet for a while.
789 """
790 commandName = 'Choke'
791 arguments = [('id', Integer())]
792 requiresAnswer = False
793
794
795class Unchoke(Command):
796 """Reverse the effects of a choke.
797 """
798 commandName = 'Unchoke'
799 arguments = [('id', Integer())]
800 requiresAnswer = False
801
802
803def safely(f, *a, **k):
804 """try/except around something, w/ twisted error handling.
805 """
806 try:
807 f(*a,**k)
808 except:
809 log.err()
810
811class Q2Q(AMP, subproducer.SuperProducer):
812 """ Quotient to Quotient protocol.
813
814 At a low level, this uses a protocol called 'Juice' (JUice Is Concurrent
815 Events), which is a simple rfc2822-inspired (although not -compliant)
816 protocol for request/response pair hookup.
817
818 At a higher level, it provides a mechanism for SSL certificate exchange,
819 looking up physical locations of users' data, and switching into other
820 protocols after an initial handshake.
821
822 @ivar publicIP: The IP that the other end of the connection claims to know
823 us by. This will be used when responding to L{Inbound} commands if the Q2Q
824 service I am attached to does not specify a public IP to use.
825
826 @ivar authorized: A boolean indicating whether SSL verification has taken
827 place to ensure that this connection's peer has claimed an accurate identity.
828 """
829
830 protocolName = 'q2q'
831 service = None
832 publicIP = None
833 authorized = False
834
835 def __init__(self, *a, **kw):
836 """ Q2Q instances should only be created by Q2QService. See
837 L{Q2QService.connectQ2Q} and L{Q2QService.listenQ2Q}.
838 """
839 subproducer.SuperProducer.__init__(self)
840 AMP.__init__(self, *a, **kw)
841
842 def connectionMade(self):
843 self.producingTransports = {}
844 self.connections = {}
845 self.listeningClient = []
846 self.connectionObservers = []
847 if self.service.publicIP is None:
848 log.msg("Service has no public IP: determining")
849 self.service.publicIP = self.transport.getHost().host
850 self.service._publicIPIsReallyPrivate = True
851 def rememberPublicIP(pubip):
852 ip = pubip['ip']
853 log.msg('remembering public ip as %r' % ip)
854 self.publicIP = ip
855 self.service.publicIP = ip
856 self.service._publicIPIsReallyPrivate = False
857 self.callRemote(SourceIP).addCallback(rememberPublicIP)
858 else:
859 log.msg("Using existing public IP: %r" % (self.service.publicIP,))
860
861 def connectionLost(self, reason):
862 ""
863 AMP.connectionLost(self, reason)
864 self._uncacheMe()
865 self.producingTransports = {}
866 for key, value in self.listeningClient:
867 log.msg("removing remote listener for %r" % (key,))
868 self.service.listeningClients[key].remove(value)
869 self.listeningClient = []
870 for xport in self.connections.values():
871 safely(xport.connectionLost, reason)
872 for observer in self.connectionObservers:
873 safely(observer)
874
875 def notifyOnConnectionLost(self, observer):
876 ""
877 self.connectionObservers.append(observer)
878
879 def _bindUDP(self, q2qsrc, q2qdst, udpsrc, udpdst, protocol):
880
881 # we are representing the src, because they are the ones being told to
882 # originate a UDP packet.
883
884 self.verifyCertificateAllowed(q2qsrc, q2qdst)
885
886 # if I've got a local factory for this 3-tuple, do the bind if I own
887 # this IP...
888 srchost, srcport = udpsrc
889
890 lcget = self.service.listeningClients.get((q2qsrc, protocol), ())
891
892 bindery = []
893
894 for (listener, listenCert, desc
895 ) in lcget:
896 # print 'looking at listener', listener
897 # print listener.transport.getPeer().host, srchost
898 if listener.transport.getPeer().host == srchost:
899 # print 'bound in clients loop'
900
901 d = listener.callRemote(
902 BindUDP,
903 q2qsrc=q2qsrc,
904 q2qdst=q2qdst,
905 udpsrc=udpsrc,
906 udpdst=udpdst,
907 protocol=protocol)
908 def swallowKnown(err):
909 err.trap(error.ConnectionDone, error.ConnectionLost)
910 d.addErrback(swallowKnown)
911 bindery.append(d)
912 if bindery:
913 # print 'bindery return', len(bindery)
914 def _justADict(ign):
915 return dict()
916 return defer.DeferredList(bindery).addCallback(_justADict)
917
918 # print 'what?', lcget
919 if (self.service.getLocalFactories(q2qdst, q2qsrc, protocol)
920 and srchost == self._determinePublicIP()):
921 self.service.dispatcher.seedNAT(udpdst, srcport, conditional=True)
922 # print 'bound locally'
923 return dict()
924 # print 'conn-error'
925 raise ConnectionError("unable to find appropriate UDP binder")
926
927 BindUDP.responder(_bindUDP)
928
929 def _identify(self, subject):
930 """
931 Implementation of L{Identify}.
932 """
933 ourCA = self.service.certificateStorage.getPrivateCertificate(str(subject))
934 return dict(certificate=ourCA)
935 Identify.responder(_identify)
936
937
938 def verifyCertificateAllowed(self,
939 ourAddress,
940 theirAddress):
941 """
942 Check that the certificate currently in use by this transport is valid to
943 claim that the connection offers authorization for this host speaking
944 for C{ourAddress}, to a host speaking for C{theirAddress}. The remote
945 host (the one claiming to use theirAddress) may have a certificate
946 which is issued for the domain for theirAddress or the full address
947 given in theirAddress.
948
949 This method runs B{after} cryptographic verification of the validity of
950 certificates, although it does not perform any cryptographic checks
951 itself. It depends on SSL connection handshaking - *and* the
952 particular certificate lookup logic which prevents spoofed Issuer
953 fields, to work properly. However, all it checks is the X509 names
954 present in the certificates matching with the application-level
955 security claims being made by our peer.
956
957 An example of successful verification, because both parties have
958 properly signed certificates for their usage from the domain they
959 have been issued::
960
961 our current certficate:
962 issuer: divmod.com
963 subject: glyph@divmod.com
964 their current certificate:
965 issuer: twistedmatrix.com
966 subject: exarkun@twistedmatrix.com
967 Arguments to verifyCertificateAllowed:
968 ourAddress: glyph@divmod.com
969 theirAddress: exarkun@twistedmatrix.com
970 Result of verifyCertificateAllowed: None
971
972 An example of rejected verification, because domain certificates are
973 always B{self}-signed in Q2Q; verisign is not a trusted certificate
974 authority for the entire internet as with some other TLS
975 implementations::
976
977 our current certificate:
978 issuer: divmod.com
979 subject: divmod.com
980 their current certificate:
981 issuer: verisign.com
982 subject: twistedmatrix.com
983 Arguments to verifyCertificateAllowed:
984 ourAddress: divmod.com
985 theirAddress: twistedmatrix.com
986 Result of verifyCertificateAllowed: exception VerifyError raised
987
988 Another example of successful verification, because we assume our
989 current certificate is under the control of this side of the
990 connection, so *any* claimed subject is considered acceptable::
991
992 our current certificate:
993 issuer: divmod.com
994 subject: divmod.com
995 their current certificate:
996 issuer: divmod.com
997 subject: glyph@twistedmatrix.com
998 Arguments to verifyCertificateAllowed:
999 ourAddress: divmod.com
1000 theirAddress: glyph@twistedmatrix.com
1001 Result of verifyCertificateAllowed: None
1002
1003 Another example of successful verification, because the user is
1004 claiming to be anonymous; there is also a somewhat looser
1005 cryptographic check applied to signatures for anonymous
1006 connections::
1007
1008 our current certificate:
1009 issuer: divmod.com
1010 subject: divmod.com
1011 their current certificate:
1012 issuer: @
1013 subject: @
1014 arguments to verifyCertificateAllowed:
1015 ourAddress: divmod.com
1016 theirAddress: @
1017 Result of verifyCertificateAllowed: None
1018
1019 Accept anonymous connections with caution.
1020
1021 @param ourAddress: a L{Q2QAddress} representing the address that we are
1022 supposed to have authority for, requested by our peer.
1023
1024 @param theirAddress: a L{Q2QAddress} representing the address that our
1025 network peer claims to be communicating on behalf of. For example, if
1026 our peer is foobar.com they may claim to be operating on behalf of any
1027 user @foobar.com.
1028
1029 @raise: L{VerifyError} if the certificates do not match the
1030 claimed addresses.
1031 """
1032
1033 # XXX TODO: Somehow, it's got to be possible for a single cluster to
1034 # internally claim to be agents of any other host when issuing a
1035 # CONNECT; in other words, we always implicitly trust ourselves. Also,
1036 # we might want to issue anonymous CONNECTs over unencrypted
1037 # connections.
1038
1039 # IOW: *we* can sign a certificate to be whoever, but the *peer* can
1040 # only sign the certificate to be the peer.
1041
1042 # The easiest way to make this work is to issue ourselves a wildcard
1043 # certificate.
1044
1045 if not self.authorized:
1046 if theirAddress.domain == '':
1047 # XXX TODO: document this rule, anonymous connections are
1048 # allowed to not be authorized because they are not making any
1049 # claims about who they are
1050
1051 # XXX also TODO: make it so that anonymous connections are
1052 # disabled by default for most protocols
1053 return True
1054 raise VerifyError("No official negotiation has taken place.")
1055
1056 peerCert = Certificate.peerFromTransport(self.transport)
1057 ourCert = self.hostCertificate
1058
1059 ourClaimedDomain = ourAddress.domainAddress()
1060 theirClaimedDomain = theirAddress.domainAddress()
1061
1062 # Sanity check #1: did we pick the right certificate on our end?
1063 if not ourClaimedDomain.claimedAsIssuerOf(ourCert):
1064 raise VerifyError(
1065 "Something has gone horribly wrong: local domain mismatch "
1066 "claim: %s actual: %s" % (ourClaimedDomain,
1067 ourCert.getIssuer()))
1068 if theirClaimedDomain.claimedAsIssuerOf(peerCert):
1069 # Their domain issued their certificate.
1070 if theirAddress.claimedAsSubjectOf(peerCert) or theirClaimedDomain.claimedAsSubjectOf(peerCert):
1071 return
1072 elif ourClaimedDomain.claimedAsIssuerOf(peerCert):
1073 # *our* domain can spoof *anything*
1074 return
1075 elif ourAddress.claimedAsIssuerOf(peerCert):
1076 # Neither our domain nor their domain signed this. Did *we*?
1077 # (Useful in peer-to-peer persistent transactions where we don't
1078 # want the server involved: exarkun@twistedmatrix.com can sign
1079 # glyph@divmod.com's certificate).
1080 return
1081
1082 raise VerifyError(
1083 "Us: %s Them: %s "
1084 "TheyClaimWeAre: %s TheyClaimTheyAre: %s" %
1085 (ourCert, peerCert,
1086 ourAddress, theirAddress))
1087
1088 def _listen(self, protocols, From, description):
1089 """
1090 Implementation of L{Listen}.
1091 """
1092 # The peer is coming from a client-side representation of the user
1093 # described by 'From', and talking *to* a server-side representation of
1094 # the user described by 'From'.
1095 self.verifyCertificateAllowed(From, From)
1096 theirCert = Certificate.peerFromTransport(self.transport)
1097 for protocolName in protocols:
1098 if protocolName.startswith('.'):
1099 raise VerifyError(
1100 "Internal protocols are for server-server use _only_: %r" %
1101 protocolName)
1102
1103 key = (From, protocolName)
1104 value = (self, theirCert, description)
1105 log.msg("%r listening for %r" % key)
1106 self.listeningClient.append((key, value))
1107 self.service.listeningClients.setdefault(key, []).append(value)
1108 return {}
1109 Listen.responder(_listen)
1110
1111
1112 def _inbound(self, From, to, protocol, udp_source=None):
1113 """
1114 Implementation of L{Inbound}.
1115 """
1116 # Verify stuff!
1117
1118 self.verifyCertificateAllowed(to, From)
1119 return self.service.verifyHook(From, to, protocol
1120 ).addCallback(self._inboundimpl,
1121 From,
1122 to,
1123 protocol,
1124 udp_source).addErrback(
1125 lambda f: f.trap(KeyError) and dict(listeners=[]))
1126 Inbound.responder(_inbound)
1127
1128 def _inboundimpl(self, ign, From, to, protocol, udp_source):
1129
1130 # 2-tuples of factory, description
1131 srvfacts = self.service.getLocalFactories(From, to, protocol)
1132
1133 result = [] # list of listener dicts
1134
1135 if srvfacts:
1136 log.msg("local factories found for inbound request: %r" % (srvfacts,))
1137 localMethods = []
1138 publicIP = self._determinePublicIP()
1139 privateIP = self._determinePrivateIP()
1140 if self.service.inboundTCPPort is not None:
1141 tcpPort = self.service.inboundTCPPort.getHost().port
1142 localMethods.append(TCPMethod(
1143 '%s:%d' %
1144 (publicIP, tcpPort)))
1145 if publicIP != privateIP:
1146 localMethods.append(TCPMethod(
1147 '%s:%d' %
1148 (privateIP, tcpPort)))
1149
1150 if not self.service.udpEnabled:
1151 log.msg("udp not enabled -- but I so want to send udp traffic!")
1152 elif udp_source is None:
1153 log.msg("udp_source was none on inbound")
1154 else:
1155 if self.service.dispatcher is None:
1156 log.msg("udp_source %s:%d, but dispatcher not running" %
1157 udp_source)
1158 else:
1159 remoteUDPHost, remoteUDPPort = udp_source
1160 log.msg(
1161 "remote PTCP: %s:%d, "
1162 "local public IP: %s, local private IP: %s"
1163 % (remoteUDPHost, remoteUDPPort, publicIP, privateIP) )
1164
1165 # Seed my NAT from my shared UDP port
1166 udpPort = self.service.dispatcher.seedNAT(udp_source, self.service.sharedUDPPortnum)
1167
1168 if remoteUDPHost == publicIP and publicIP != privateIP:
1169 log.msg(
1170 "Remote IP matches local, public IP %r;"
1171 " preferring internal IP %r" % (publicIP, privateIP))
1172 localMethods.append(
1173 PTCPMethod("%s:%d" % (privateIP, udpPort)))
1174 localMethods.append(
1175 PTCPMethod("%s:%d" % (publicIP, udpPort)))
1176
1177 # XXX CLEANUP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1178 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1179 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1180 privateUDPPort = self.service.dispatcher.seedNAT(udp_source)
1181 localMethods.append(
1182 PTCPMethod('%s:%d' % (publicIP, privateUDPPort)))
1183
1184 udpxPort = self.service.dispatcher.seedNAT(udp_source)
1185 localMethods.append(
1186 RPTCPMethod("%s:%d" % (publicIP, udpxPort)))
1187
1188 if self.service.virtualEnabled:
1189 localMethods.append(VirtualMethod())
1190 log.msg('responding to inbound with local methods: %r' % (localMethods,))
1191
1192 for serverFactory, description in srvfacts:
1193 expiryTime, listenID = self.service.mapListener(
1194 to, From, protocol, serverFactory)
1195 result.append(dict(id=listenID,
1196 expires=expiryTime,
1197 methods=localMethods,
1198 description=description))
1199
1200 # We've looked for our local factory. Let's see if we have any
1201 # listening protocols elsewhere.
1202
1203
1204 key = (to, protocol)
1205 if key in self.service.listeningClients:
1206 args = dict(From=From,
1207 to=to,
1208 protocol=protocol,
1209 udp_source=udp_source)
1210 DL = []
1211 lclients = self.service.listeningClients[key]
1212 log.msg("listeners found for %s:%r" % (to, protocol))
1213 for listener, listenCert, desc in lclients:
1214 log.msg("relaying inbound to %r via %r" % (to, listener))
1215 DL.append(listener.callRemote(Inbound, **args).addCallback(
1216 self._massageClientInboundResponse, listener, result))
1217
1218 def allListenerResponses(x):
1219 log.msg("all inbound responses received: %s" % (pformat(result),))
1220 return dict(listeners=result)
1221 return defer.DeferredList(DL).addCallback(allListenerResponses)
1222 else:
1223 log.msg("no listenening clients for %s:%r. local methods: %r" % (to,protocol, result))
1224 return dict(listeners=result)
1225
1226
1227 def _massageClientInboundResponse(self, inboundResponse, listener, result):
1228 irl = inboundResponse['listeners']
1229 log.msg("received relayed inbound response: %r via %r" %
1230 (inboundResponse, listener))
1231
1232 for listenerInfo in irl:
1233 # inboundResponse['description'] = ??? trust client version for
1234 # now... maybe the server doesn't even need to know about
1235 # descriptions...?
1236 listenerInfo['methods'] = [
1237 meth for meth in listenerInfo['methods'] if meth.relayable]
1238 # make sure that the certificate that we're relaying matches the
1239 # certificate that they gave us!
1240 if listenerInfo['methods']:
1241 allowedCertificate = Certificate.peerFromTransport(
1242 listener.transport)
1243 listenerInfo['certificate'] = allowedCertificate
1244 result.append(listenerInfo)
1245
1246 def _determinePublicIP(self):
1247 reservePublicIP = None
1248 if self.service.publicIP is not None:
1249 if self.service._publicIPIsReallyPrivate:
1250 reservePublicIP = self.service.publicIP
1251 else:
1252 return self.service.publicIP
1253 if self.publicIP is not None:
1254 return self.publicIP
1255 if reservePublicIP is not None:
1256 return reservePublicIP
1257 return self._determinePrivateIP()
1258
1259 def _determinePrivateIP(self):
1260 return self.transport.getHost().host
1261
1262 def _sourceIP(self):
1263 result = {'ip': self.transport.getPeer().host}
1264 return result
1265 SourceIP.responder(_sourceIP)
1266
1267 def _resume(self, connection, data, writeDeferred):
1268 try:
1269 connection.dataReceived(data)
1270 except:
1271 writeDeferred.errback()
1272 else:
1273 writeDeferred.callback({})
1274
1275
1276 def _choke(self, id):
1277 connection = self.connections[id]
1278 connection.choke()
1279 return {}
1280 Choke.responder(_choke)
1281
1282
1283 def _unchoke(self, id):
1284 connection = self.connections[id]
1285 connection.unchoke()
1286 return {}
1287 Unchoke.responder(_unchoke)
1288
1289
1290 def amp_WRITE(self, box):
1291 """
1292 Respond to a WRITE command, sending some data over a virtual channel
1293 created by VIRTUAL. The answer is simply an acknowledgement, as it is
1294 simply meant to note that the write went through without errors.
1295
1296 An occurrence of I{Write} on the wire, together with the response
1297 generated by this method, might have this apperance::
1298
1299 C: -Command: Write
1300 C: -Ask: 1
1301 C: -Length: 13
1302 C: Id: glyph@divmod.com->radix@twistedmatrix.com:q2q-example:0
1303 C:
1304 C: HELLO WORLD
1305 C:
1306 S: -Answer: 1
1307 S:
1308
1309 """
1310 id = int(box['id'])
1311 if id not in self.connections:
1312 raise error.ConnectionDone()
1313 connection = self.connections[id]
1314 data = box['body']
1315 connection.dataReceived(data)
1316 return AmpBox()
1317
1318 def amp_CLOSE(self, box):
1319 """
1320 Respond to a CLOSE command, dumping some data onto the stream. As with
1321 WRITE, this returns an empty acknowledgement.
1322
1323 An occurrence of I{Close} on the wire, together with the response
1324 generated by this method, might have this apperance::
1325
1326 C: -Command: Close
1327 C: -Ask: 1
1328 C: Id: glyph@divmod.com->radix@twistedmatrix.com:q2q-example:0
1329 C:
1330 S: -Answer: 1
1331 S:
1332
1333 """
1334 # The connection is removed from the mapping by connectionLost.
1335 connection = self.connections[int(box['id'])]
1336 connection.connectionLost(Failure(CONNECTION_DONE))
1337 return AmpBox()
1338
1339
1340 def _sign(self, certificate_request, password):
1341 """
1342 Respond to a request to sign a CSR for a user or agent located within
1343 our domain.
1344 """
1345 if self.service.portal is None:
1346 raise BadCertificateRequest("This agent cannot sign certificates.")
1347
1348 subj = certificate_request.getSubject()
1349
1350 sk = subj.keys()
1351 if 'commonName' not in sk:
1352 raise BadCertificateRequest(
1353 "Certificate requested with bad subject: %s" % (sk,))
1354
1355 uandd = subj.commonName.split("@")
1356 if len(uandd) != 2:
1357 raise BadCertificateRequest("Won't sign certificates for other domains")
1358 domain = uandd[1]
1359
1360 CS = self.service.certificateStorage
1361 ourCert = CS.getPrivateCertificate(domain)
1362
1363 D = self.service.portal.login(
1364 UsernamePassword(subj.commonName,
1365 password),
1366 self,
1367 ivertex.IQ2QUser)
1368
1369 def _(ial):
1370 (iface, aspect, logout) = ial
1371 ser = CS.genSerial(domain)
1372 return dict(certificate=aspect.signCertificateRequest(
1373 certificate_request, ourCert, ser))
1374
1375 return D.addCallback(_)
1376 Sign.responder(_sign)
1377
1378
1379 def _secure(self, to, From, authorize):
1380 """
1381 Response to a SECURE command, starting TLS when necessary, and using a
1382 certificate identified by the I{To} header.
1383
1384 An occurrence of I{Secure} on the wire, together with the response
1385 generated by this method, might have the following appearance::
1386
1387 C: -Command: Secure
1388 C: -Ask: 1
1389 C: To: divmod.com
1390 C: From: twistedmatrix.com
1391 C: Authorize: True
1392 C:
1393 Client Starts TLS here with twistedmatrix.com certificate
1394 S: -Answer: 1
1395 S:
1396 Server Starts TLS here with divmod.com certificate
1397
1398 """
1399 if self.hostCertificate is not None:
1400 raise RuntimeError("Re-encrypting already encrypted connection")
1401 CS = self.service.certificateStorage
1402 ourCert = CS.getPrivateCertificate(str(to.domainAddress()))
1403 if authorize:
1404 D = CS.getSelfSignedCertificate(str(From.domainAddress()))
1405 else:
1406 self.authorized = False
1407 return {'tls_localCertificate': ourCert}
1408
1409 def hadCert(peerSigned):
1410 self.authorized = True
1411 self._cacheMeNow(From, to, authorize)
1412 return {'tls_localCertificate': ourCert,
1413 'tls_verifyAuthorities': [peerSigned]}
1414
1415 def didNotHaveCert(err):
1416 err.trap(KeyError)
1417 return self._retrieveRemoteCertificate(From, port)
1418
1419 D.addErrback(didNotHaveCert)
1420 D.addCallback(hadCert)
1421
1422 return D
1423 Secure.responder(_secure)
1424
1425 _cachedUnrequested = False
1426
1427 def _cacheMeNow(self, From, to, authorize):
1428 tcpeer = self.transport.getPeer()
1429 # XXX 'port' is insane here, but we lack a better number to hash
1430 # against. perhaps the SECURE request should give a reciprocal
1431 # connection identifier...?
1432 self.service.secureConnectionCache.cacheUnrequested(
1433 endpoint.TCPEndpoint(tcpeer.host, port),
1434 (From, to.domain, authorize), self)
1435 assert not self._cachedUnrequested
1436 self._cachedUnrequested = (From, to, authorize, tcpeer)
1437
1438 def _uncacheMe(self):
1439 if self._cachedUnrequested:
1440 # If this is a client connection, this will never be called, since
1441 # _cacheMeNow is called from the _server_ half of this business.
1442 # The uncaching API here is a bit of a ragged edge of conncache.py;
1443 # the interface should probably be cleaned up, but I don't think
1444 # there are any functional problems with it.
1445 From, to, authorize, tcpeer = self._cachedUnrequested
1446 self.service.secureConnectionCache.connectionLostForKey(
1447 (endpoint.TCPEndpoint(tcpeer.host, port),
1448 (From, to.domain, authorize)))
1449
1450 def _retrieveRemoteCertificate(self, From, port=port):
1451 """
1452 The entire conversation, starting with TCP handshake and ending at
1453 disconnect, to retrieve a foreign domain's certificate for the first
1454 time.
1455 """
1456 CS = self.service.certificateStorage
1457 host = str(From.domainAddress())
1458 p = AMP()
1459 p.wrapper = self.wrapper
1460 f = protocol.ClientCreator(reactor, lambda: p)
1461 connD = f.connectTCP(host, port)
1462
1463 def connected(proto):
1464 dhost = From.domainAddress()
1465 iddom = proto.callRemote(Identify, subject=dhost)
1466 def gotCert(identifyBox):
1467 theirCert = identifyBox['certificate']
1468 theirIssuer = theirCert.getIssuer().commonName
1469 theirName = theirCert.getSubject().commonName
1470 if (theirName != str(dhost)):
1471 raise VerifyError(
1472 "%r claimed it was %r in IDENTIFY response"
1473 % (theirName, dhost))
1474 if (theirIssuer != str(dhost)):
1475 raise VerifyError(
1476 "self-signed %r claimed it was issued by "
1477 "%r in IDENTIFY response" % (dhost, theirIssuer))
1478 def storedCert(ignored):
1479 return theirCert
1480 return CS.storeSelfSignedCertificate(
1481 str(dhost), theirCert).addCallback(storedCert)
1482 def nothingify(x):
1483 proto.transport.loseConnection()
1484 return x
1485 return iddom.addCallback(gotCert).addBoth(nothingify)
1486 connD.addCallback(connected)
1487 return connD
1488
1489
1490 def secure(self, fromAddress, toAddress,
1491 fromCertificate, foreignCertificateAuthority=None,
1492 authorize=True):
1493 """Return a Deferred which fires True when this connection has been secured as
1494 a channel between fromAddress (locally) and toAddress (remotely).
1495 Raises an error if this is not possible.
1496 """
1497 if self.hostCertificate is not None:
1498 raise RuntimeError("Re-securing already secured connection.")
1499
1500 def _cbSecure(response):
1501 if foreignCertificateAuthority is not None:
1502 self.authorized = True
1503 return True
1504 extra = {'tls_localCertificate': fromCertificate}
1505 if foreignCertificateAuthority is not None:
1506 extra['tls_verifyAuthorities'] = [foreignCertificateAuthority]
1507
1508 return self.callRemote(
1509 Secure,
1510 From=fromAddress,
1511 to=toAddress,
1512 authorize=authorize, **extra).addCallback(_cbSecure)
1513
1514 def _virtual(self, id):
1515 if self.isServer:
1516 assert id > 0
1517 else:
1518 assert id < 0
1519 # We are double-deferring here so that we only start writing data to
1520 # our client _after_ they have processed our ACK.
1521 tpt = VirtualTransport(self, id, self.service._bootstrapFactory, False)
1522
1523
1524 return dict(__transport__=tpt)
1525
1526 Virtual.responder(_virtual)
1527
1528
1529 # Client/Support methods.
1530
1531 def attemptConnectionMethods(self, methods, connectionID, From, to,
1532 protocolName, protocolFactory):
1533 attemptObjects = []
1534 for meth in methods:
1535 atts = meth.attempt(self, connectionID, From, to,
1536 protocolName, protocolFactory)
1537 attemptObjects.extend(atts)
1538
1539 attemptDeferreds = [att.startAttempt() for att in attemptObjects]
1540
1541 d = defer.DeferredList(attemptDeferreds,
1542 fireOnOneCallback=True,
1543 fireOnOneErrback=False)
1544 def dontLogThat(e):
1545 e.trap(error.ConnectionLost, error.ConnectionDone)
1546
1547 for attDef in attemptDeferreds:
1548 attDef.addErrback(dontLogThat)
1549
1550 def _unfortunate_defer_hack(results):
1551 #Do you see what you've made me become?
1552 if isinstance(results, tuple):
1553 stuff = [(False, None)] * len(attemptObjects)
1554 stuff[results[1]] = (True, results[0])
1555 return stuff
1556 return results
1557
1558
1559 def gotResults(results):
1560 theResult = None
1561 anyResult = False
1562 for index, (success, result) in enumerate(results):
1563 if success:
1564 # woohoo! home free.
1565 # XXX Cancel outstanding attempts, maybe. They'll fail anyway,
1566 # because the factory will return None from buildProtocol().
1567 theResult = result
1568 anyResult = True
1569 else:
1570 attemptObjects[index].cancel()
1571 if anyResult:
1572 # theResult will be a SeparateConnectionTransport
1573 return theResult.subProtocol
1574 else:
1575 reason = Failure(AttemptsFailed([fobj for (f, fobj) in results]))
1576 return reason
1577
1578 d.addCallback(_unfortunate_defer_hack)
1579 d.addCallback(gotResults)
1580 return d
1581
1582
1583 def listen(self, fromAddress, protocols, serverDescription):
1584 return self.callRemote(
1585 Listen, From=fromAddress,
1586 protocols=protocols, description=serverDescription)
1587
1588
1589 def connect(self, From, to,
1590 protocolName, clientFactory,
1591 chooser):
1592 """
1593 Issue an INBOUND command, creating a virtual connection to the peer,
1594 given identifying information about the endpoint to connect to, and a
1595 protocol factory.
1596
1597 @param clientFactory: a *Client* ProtocolFactory instance which will
1598 generate a protocol upon connect.
1599
1600 @return: a Deferred which fires with the protocol instance that was
1601 connected, or fails with AttemptsFailed if the connection was not
1602 possible.
1603 """
1604
1605 publicIP = self._determinePublicIP()
1606
1607 A = dict(From=From,
1608 to=to,
1609 protocol=protocolName)
1610
1611 if self.service.dispatcher is not None:
1612 # tell them exactly where they can shove it
1613 A['udp_source'] = (publicIP,
1614 self.service.sharedUDPPortnum)
1615 else:
1616 # don't tell them because we don't know
1617 log.msg("dispatcher unavailable when connecting")
1618
1619 D = self.callRemote(Inbound, **A)
1620
1621 def _connected(answer):
1622 listenersD = defer.maybeDeferred(chooser, answer['listeners'])
1623 def gotListeners(listeners):
1624 allConnectionAttempts = []
1625 for listener in listeners:
1626 d = self.attemptConnectionMethods(
1627 listener['methods'],
1628 listener['id'],
1629 From, to,
1630 protocolName, clientFactory,
1631 )
1632 allConnectionAttempts.append(d)
1633 return defer.DeferredList(allConnectionAttempts)
1634 listenersD.addCallback(gotListeners)
1635 def finishedAllAttempts(results):
1636 succeededAny = False
1637 failures = []
1638 if not results:
1639 return Failure(NoAttemptsMade(
1640 "there was no available path for connections "
1641 "(%r->%r/%s)" % (From, to, protocolName)))
1642 for succeeded, result in results:
1643 if succeeded:
1644 succeededAny = True
1645 randomConnection = result
1646 break
1647 else:
1648 failures.append(result)
1649 if not succeededAny:
1650 return Failure(AttemptsFailed(
1651 [failure.getBriefTraceback() for failure in failures]))
1652
1653 # XXX TODO: this connection is really random; connectQ2Q should
1654 # not return one of the connections it's made, put it into your
1655 # protocol's connectionMade handler
1656
1657 return randomConnection
1658
1659 return listenersD.addCallback(finishedAllAttempts)
1660 return D.addCallback(_connected)
1661
1662
1663class SeparateConnectionTransport(object):
1664 def __init__(self,
1665 service,
1666 subProtocol,
1667 q2qhost,
1668 q2qpeer,
1669 protocolName,
1670 connectionEstablishedDeferred=None):
1671 self.service = service
1672 self.subProtocol = subProtocol
1673 self.q2qhost = q2qhost
1674 self.q2qpeer = q2qpeer
1675 self.protocolName = protocolName
1676 self.connectionEstablishedDeferred = connectionEstablishedDeferred
1677
1678 subProtocol = None
1679 q2qhost = None
1680 q2qpeer = None
1681 protocolName = 'unknown'
1682
1683 # ITransport
1684 disconnecting = property(lambda self: self.transport.disconnecting)
1685
1686 # IQ2QTransport
1687
1688 def getQ2QHost(self):
1689 return self.q2qhost
1690
1691 def getQ2QPeer(self):
1692 return self.q2qpeer
1693
1694 def makeConnection(self, tpt):
1695 self.transport = tpt
1696 self.service.subConnections.append(self)
1697 self.subProtocol.makeConnection(self)
1698 if self.connectionEstablishedDeferred is not None:
1699 self.connectionEstablishedDeferred.callback(self)
1700
1701 def getPeer(self):
1702 return Q2QTransportAddress(self.getQ2QPeer(),
1703 self.transport.getPeer(),
1704 self.protocolName)
1705
1706 def getHost(self):
1707 return Q2QTransportAddress(self.getQ2QHost(),
1708 self.transport.getHost(),
1709 self.protocolName)
1710
1711 def dataReceived(self, data):
1712 self.subProtocol.dataReceived(data)
1713
1714 def write(self, data):
1715 self.transport.write(data)
1716
1717 def writeSequence(self, data):
1718 self.transport.writeSequence(data)
1719
1720 def registerProducer(self, producer, streaming):
1721 self.transport.registerProducer(producer, streaming)
1722
1723 def unregisterProducer(self):
1724 self.transport.unregisterProducer()
1725
1726 def loseConnection(self):
1727 self.transport.loseConnection()
1728
1729 def connectionLost(self, reason):
1730 self.service.subConnections.remove(self)
1731 if self.subProtocol is not None:
1732 self.subProtocol.connectionLost(reason)
1733 self.subProtocol = None
1734
1735class WhoAmI(Command):
1736 commandName = 'Who-Am-I'
1737
1738 response = [
1739 ('address', HostPort()),
1740 ]
1741
1742class RetrieveConnection(ProtocolSwitchCommand):
1743 commandName = 'Retrieve-Connection'
1744
1745 arguments = [
1746 ('identifier', String()),
1747 ]
1748
1749 fatalErrors = {KeyError: "NoSuchConnection"}
1750
1751class Q2QBootstrap(AMP):
1752 def __init__(self, connIdentifier=None, protoFactory=None):
1753 AMP.__init__(self)
1754 assert connIdentifier is None or isinstance(connIdentifier, (str))
1755 self.connIdentifier = connIdentifier
1756 self.protoFactory = protoFactory
1757
1758 def connectionMade(self):
1759 if self.connIdentifier is not None:
1760 def swallowKnown(err):
1761 err.trap(error.ConnectionDone, KeyError)
1762 self.retrieveConnection(self.connIdentifier, self.protoFactory).addErrback(swallowKnown)
1763
1764 def whoami(self):
1765 """Return a Deferred which fires with a 2-tuple of (dotted quad ip, port
1766 number).
1767 """
1768 def cbWhoAmI(result):
1769 return result['address']
1770 return self.callRemote(WhoAmI).addCallback(cbWhoAmI)
1771
1772
1773 def _whoami(self):
1774 peer = self.transport.getPeer()
1775 return {
1776 'address': (peer.host, peer.port),
1777 }
1778 WhoAmI.responder(_whoami)
1779
1780
1781 def retrieveConnection(self, identifier, factory):
1782 return self.callRemote(RetrieveConnection, factory, identifier=identifier)
1783
1784
1785 def _retrieveConnection(self, identifier):
1786 listenerInfo = self.service.lookupListener(identifier)
1787 if listenerInfo is None:
1788 raise KeyError(identifier)
1789 else:
1790 proto = listenerInfo.protocolFactory.buildProtocol(listenerInfo.From)
1791 return SeparateConnectionTransport(
1792 self.service,
1793 proto,
1794 listenerInfo.to,
1795 listenerInfo.From,
1796 listenerInfo.protocolName)
1797
1798 RetrieveConnection.responder(_retrieveConnection)
1799
1800
1801
1802class Q2QBootstrapFactory(protocol.Factory):
1803 protocol = Q2QBootstrap
1804
1805 def __init__(self, service):
1806 self.service = service
1807
1808 def buildProtocol(self, addr):
1809 q2etc = protocol.Factory.buildProtocol(self, addr)
1810 q2etc.service = self.service
1811 return q2etc
1812
1813class VirtualTransport(subproducer.SubProducer):
1814 implements(interfaces.IProducer, interfaces.ITransport, interfaces.IConsumer)
1815 disconnecting = False
1816
1817 def __init__(self, q2q, connectionID, protocolFactory, isClient):
1818 """
1819 @param q2q: a Q2Q Protocol instance.
1820
1821 @param connectionID: an integer identifier, unique to the q2q instance
1822 that I am wrapping (my underlying physical connection).
1823
1824 @param protocolFactory: an IProtocolFactory implementor which returns a
1825 protocol instance for me to use. I'll use it to build the protocol,
1826 and if the 'client' flag is True, also use it to notify
1827 connectionLost/connectionFailed.
1828
1829 @param isClient: a boolean describing whether my protocol is the
1830 initiating half of this connection or not.
1831 """
1832 subproducer.SubProducer.__init__(self, q2q)
1833 self.q2q = q2q
1834
1835 self.id = connectionID
1836 self.isClient = isClient
1837 self.q2q.connections[self.id] = self
1838 self.protocolFactory = protocolFactory
1839
1840 protocol = None
1841
1842 def startProtocol(self):
1843 self.protocol = self.protocolFactory.buildProtocol(self.getPeer())
1844 self.protocol.makeConnection(self)
1845 return self.protocol
1846
1847 def pauseProducing(self):
1848 self.q2q.callRemote(Choke, id=self.id)
1849
1850 def resumeProducing(self):
1851 self.q2q.callRemote(Unchoke, id=self.id)
1852
1853 def writeSequence(self, iovec):
1854 self.write(''.join(iovec))
1855
1856 def loseConnection(self):
1857 if self.disconnecting:
1858 # print 'omg wtf loseConnection!???!'
1859 return
1860 self.disconnecting = True
1861 d = self.q2q.callRemoteString('close', id=str(self.id))
1862 def cbClosed(ignored):
1863 self.connectionLost(Failure(CONNECTION_DONE))
1864 def ebClosed(reason):
1865 if self.id in self.q2q.connections:
1866 self.connectionLost(reason)
1867 elif not reason.check(error.ConnectionDone):
1868 # Anything but a ConnectionDone (or similar things, perhaps)
1869 # is fishy. Like an IndexError, that'd be wacko. But a
1870 # ConnectionDone when self.id is already out of the Q2Q's
1871 # connections mapping means the connection was closed after
1872 # we thought it was supposed to be closed. No harm there.
1873 log.err(reason, "Close virtual #%d failed" % (self.id,))
1874 d.addCallbacks(cbClosed, ebClosed)
1875
1876
1877 def connectionLost(self, reason):
1878 del self.q2q.connections[self.id]
1879 if self.protocol is not None:
1880 self.protocol.connectionLost(reason)
1881 if self.isClient:
1882 self.protocolFactory.clientConnectionLost(None, reason)
1883
1884
1885 def dataReceived(self, data):
1886 try:
1887 self.protocol.dataReceived(data)
1888 except:
1889 # XXX: unconditionally logging errors from user code makes it hard
1890 # to write tests, and is not always the right thing to do. we
1891 # should revamp Twisted to have some kind of control over this
1892 # behavior, and add that control back in to this code path as well
1893 # (although logging exceptions from dataReceived is _by default_
1894 # certainly the right thing to do) --glyph+exarkun
1895 reason = Failure()
1896 log.err(reason)
1897 self.connectionLost(reason)
1898
1899 def write(self, data):
1900 self.q2q.callRemoteString(
1901 'write', False, body=data, id=str(self.id))
1902
1903 def getHost(self):
1904 return VirtualTransportAddress(self.q2q.transport.getHost())
1905
1906 def getPeer(self):
1907 return VirtualTransportAddress(self.q2q.transport.getPeer())
1908
1909
1910_counter = 0
1911def _nextJuiceLog():
1912 global _counter
1913 try:
1914 return str(_counter)
1915 finally:
1916 _counter = _counter + 1
1917
1918class DefaultQ2QAvatar:
1919 implements(ivertex.IQ2QUser)
1920
1921 def __init__(self, username, domain):
1922 self.username = username
1923 self.domain = domain
1924
1925 def signCertificateRequest(self, certificateRequest,
1926 domainCert, suggestedSerial):
1927 keyz = certificateRequest.getSubject().keys()
1928 if keyz != ['commonName']:
1929 raise BadCertificateRequest(
1930 "Don't know how to verify fields other than CN: " +
1931 repr(keyz))
1932 newCert = domainCert.signRequestObject(
1933 certificateRequest,
1934 suggestedSerial)
1935 log.msg('signing certificate for user %s@%s: %s' % (
1936 self.username, self.domain, newCert.digest()))
1937 return newCert
1938
1939
1940
1941class DefaultCertificateStore:
1942
1943 implements(ICredentialsChecker, IRealm)
1944
1945 credentialInterfaces = [IUsernamePassword]
1946
1947 def requestAvatar(self, avatarId, mind, interface):
1948 assert interface is ivertex.IQ2QUser, (
1949 "default certificate store only supports one interface")
1950 return interface, DefaultQ2QAvatar(*avatarId.split("@")), lambda : None
1951
1952 def requestAvatarId(self, credentials):
1953 username, domain = credentials.username.split("@")
1954 pw = self.users.get((domain, username))
1955 if pw is None:
1956 return defer.fail(UnauthorizedLogin())
1957 def _(passwordIsCorrect):
1958 if passwordIsCorrect:
1959 return username + '@' + domain
1960 else:
1961 raise UnauthorizedLogin()
1962 return defer.maybeDeferred(
1963 credentials.checkPassword, pw).addCallback(_)
1964
1965 def __init__(self):
1966 self.remoteStore = {}
1967 self.localStore = {}
1968 self.users = {}
1969
1970 def getSelfSignedCertificate(self, domainName):
1971 return defer.maybeDeferred(self.remoteStore.__getitem__, domainName)
1972
1973 def addUser(self, domain, username, privateSecret):
1974 self.users[domain, username] = privateSecret
1975
1976 def checkUser(self, domain, username, privateSecret):
1977 if self.users.get((domain, username)) != privateSecret:
1978 return defer.fail(KeyError())
1979 return defer.succeed(True)
1980
1981 def storeSelfSignedCertificate(self, domainName, mainCert):
1982 """
1983
1984 @return: a Deferred which will fire when the certificate has been
1985 stored successfully.
1986 """
1987 assert not isinstance(mainCert, str)
1988 return defer.maybeDeferred(self.remoteStore.__setitem__, domainName, mainCert)
1989
1990 def getPrivateCertificate(self, domainName):
1991 """
1992
1993 @return: a PrivateCertificate instance, e.g. a certificate including a
1994 private key, for 'domainName'.
1995 """
1996 return self.localStore[domainName]
1997
1998
1999 def genSerial(self, name):
2000 return abs(struct.unpack('!i', md5(name).digest()[:4])[0])
2001
2002 def addPrivateCertificate(self, subjectName, existingCertificate=None):
2003 """
2004 Add a PrivateCertificate object to this store for this subjectName.
2005
2006 If existingCertificate is None, add a new self-signed certificate.
2007 """
2008 if existingCertificate is None:
2009 assert '@' not in subjectName, "Don't self-sign user certs!"
2010 mainDN = DistinguishedName(commonName=subjectName)
2011 mainKey = KeyPair.generate()
2012 mainCertReq = mainKey.certificateRequest(mainDN)
2013 mainCertData = mainKey.signCertificateRequest(mainDN, mainCertReq,
2014 lambda dn: True,
2015 self.genSerial(subjectName))
2016 mainCert = mainKey.newCertificate(mainCertData)
2017 else:
2018 mainCert = existingCertificate
2019 self.localStore[subjectName] = mainCert
2020
2021import os
2022
2023class _pemmap(object):
2024 def __init__(self, pathname, certclass):
2025 self.pathname = pathname
2026 try:
2027 os.makedirs(pathname)
2028 except (OSError, IOError):
2029 pass
2030 self.certclass = certclass
2031
2032 def file(self, name, mode):
2033 try:
2034 return file(os.path.join(self.pathname, name)+'.pem', mode)
2035 except IOError, ioe:
2036 raise KeyError(name, ioe)
2037
2038 def __setitem__(self, key, cert):
2039 kn = cert.getSubject().commonName
2040 assert kn == key
2041 self.file(kn, 'wb').write(cert.dumpPEM())
2042
2043 def __getitem__(self, cn):
2044 return self.certclass.loadPEM(self.file(cn, 'rb').read())
2045
2046 def iteritems(self):
2047 files = os.listdir(self.pathname)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: