chiark / gitweb /
server/tests.at: Fix TRIPECTL_INTERACT argument order.
[tripe] / svc / connect.in
CommitLineData
a62f8e8a
MW
1#! @PYTHON@
2### -*-python-*-
3###
4### Service for establishing dynamic connections
5###
6### (c) 2006 Straylight/Edgeware
7###
8
9###----- Licensing notice ---------------------------------------------------
10###
11### This file is part of Trivial IP Encryption (TrIPE).
12###
13### TrIPE is free software; you can redistribute it and/or modify
14### it under the terms of the GNU General Public License as published by
15### the Free Software Foundation; either version 2 of the License, or
16### (at your option) any later version.
17###
18### TrIPE is distributed in the hope that it will be useful,
19### but WITHOUT ANY WARRANTY; without even the implied warranty of
20### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21### GNU General Public License for more details.
22###
23### You should have received a copy of the GNU General Public License
24### along with TrIPE; if not, write to the Free Software Foundation,
25### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27VERSION = '@VERSION@'
28
29###--------------------------------------------------------------------------
30### External dependencies.
31
32from optparse import OptionParser
33import tripe as T
34import os as OS
35import cdb as CDB
36import mLib as M
37from time import time
38
39S = T.svcmgr
40
41###--------------------------------------------------------------------------
42### Main service machinery.
43
44_magic = ['_magic'] # An object distinct from all others
45
46class Peer (object):
47 """Representation of a peer in the database."""
48
49 def __init__(me, peer, cdb = None):
50 """
51 Create a new peer, named PEER.
52
53 Information about the peer is read from the database CDB, or the default
54 one given on the command-line.
55 """
56 me.name = peer
57 try:
58 record = (cdb or CDB.init(opts.cdb))['P' + peer]
59 except KeyError:
60 raise T.TripeJobError('unknown-peer', peer)
61 me.__dict__.update(M.URLDecode(record, semip = True))
62
63 def get(me, key, default = _magic):
64 """
65 Get the information stashed under KEY from the peer's database record.
66
67 If DEFAULT is given, then use it if the database doesn't contain the
68 necessary information. If no DEFAULT is given, then report an error.
69 """
70 attr = me.__dict__.get(key, default)
71 if attr is _magic:
72 raise T.TripeJobError('malformed-peer', me.name, 'missing-key', key)
73 return attr
74
75 def list(me):
76 """
77 Iterate over the available keys in the peer's database record.
78 """
79 return me.__dict__.iterkeys()
80
81def addpeer(peer, addr):
82 """
83 Process a connect request from a new peer PEER on address ADDR.
84
85 Any existing peer with this name is disconnected from the server.
86 """
87 if peer.name in S.list():
88 S.kill(peer.name)
89 try:
90 S.add(peer.name,
91 tunnel = peer.get('tunnel', None),
92 keepalive = peer.get('keepalive', None),
48b84569 93 key = peer.get('key', None),
a62f8e8a
MW
94 cork = peer.get('cork', 'nil') in ['t', 'true', 'y', 'yes', 'on'],
95 *addr)
96 except T.TripeError, exc:
97 raise T.TripeJobError(*exc.args)
98
99def cmd_active(name):
100 """
101 active NAME: Handle an active connection request for the peer called NAME.
102
103 The appropriate address is read from the database automatically.
104 """
105 peer = Peer(name)
106 addr = peer.get('peer')
107 if addr == 'PASSIVE':
108 raise T.TripeJobError('passive-peer', name)
109 addpeer(peer, M.split(addr, quotep = True)[0])
110
111def cmd_list():
112 """
113 list: Report a list of the available active peers.
114 """
115 cdb = CDB.init(opts.cdb)
116 for key in cdb.keys():
117 if key.startswith('P') and Peer(key[1:]).get('peer', '') != 'PASSIVE':
118 T.svcinfo(key[1:])
119
120def cmd_info(name):
121 """
122 info NAME: Report the database entries for the named peer.
123 """
124 peer = Peer(name)
125 items = list(peer.list())
126 items.sort()
127 for i in items:
128 T.svcinfo('%s=%s' % (i, peer.get(i)))
129
130## Dictionary mapping challenges to waiting passive-connection coroutines.
131chalmap = {}
132
133def cmd_passive(*args):
134 """
135 passive [OPTIONS] USER: Await the arrival of the named USER.
136
137 Report a challenge; when (and if!) the server receives a greeting quoting
138 this challenge, add the corresponding peer to the server.
139 """
140 timeout = 30
141 op = T.OptParse(args, ['-timeout'])
142 for opt in op:
143 if opt == '-timeout':
144 timeout = T.timespec(op.arg())
145 user, = op.rest(1, 1)
146 try:
147 peer = CDB.init(opts.cdb)['U' + user]
148 except KeyError:
149 raise T.TripeJobError('unknown-user', user)
150 chal = S.getchal()
151 cr = T.Coroutine.getcurrent()
152 timer = M.SelTimer(time() + timeout, lambda: cr.switch(None))
153 try:
154 T.svcinfo(chal)
155 chalmap[chal] = cr
156 addr = cr.parent.switch()
157 if addr is None:
158 raise T.TripeJobError('connect-timeout')
159 addpeer(Peer(peer), addr)
160 finally:
161 del chalmap[chal]
162
163def notify(_, code, *rest):
164 """
165 Watch for notifications.
166
167 In particular, if a GREETing appears quoting a challenge in the chalmap
168 then wake up the corresponding coroutine.
169 """
170 if code != 'GREET':
171 return
172 chal = rest[0]
173 addr = rest[1:]
174 if chal in chalmap:
175 chalmap[chal].switch(addr)
176
177###--------------------------------------------------------------------------
178### Start up.
179
180def setup():
181 """
182 Service setup.
183
184 Register the notification-watcher, and add the automatic active peers.
185 """
186 S.handler['NOTE'] = notify
187 S.watch('+n')
188 if opts.startup:
189 cdb = CDB.init(opts.cdb)
190 try:
191 autos = cdb['%AUTO']
192 except KeyError:
193 autos = ''
194 for name in M.split(autos)[0]:
195 try:
196 peer = Peer(name, cdb)
197 addpeer(peer, M.split(peer.get('peer'), quotep = True)[0])
198 except T.TripeJobError, err:
199 S.warn('connect', 'auto-add-failed', name, *err.args)
200
201def parse_options():
202 """
203 Parse the command-line options.
204
205 Automatically changes directory to the requested configdir, and turns on
206 debugging. Returns the options object.
207 """
208 op = OptionParser(usage = '%prog [-a FILE] [-d DIR]',
209 version = '%%prog %s' % VERSION)
210
211 op.add_option('-a', '--admin-socket',
212 metavar = 'FILE', dest = 'tripesock', default = T.tripesock,
213 help = 'Select socket to connect to [default %default]')
214 op.add_option('-d', '--directory',
215 metavar = 'DIR', dest = 'dir', default = T.configdir,
216 help = 'Select current diretory [default %default]')
217 op.add_option('-p', '--peerdb',
218 metavar = 'FILE', dest = 'cdb', default = T.peerdb,
219 help = 'Select peers database [default %default]')
220 op.add_option('--daemon', dest = 'daemon',
221 default = False, action = 'store_true',
222 help = 'Become a daemon after successful initialization')
223 op.add_option('--debug', dest = 'debug',
224 default = False, action = 'store_true',
225 help = 'Emit debugging trace information')
226 op.add_option('--startup', dest = 'startup',
227 default = False, action = 'store_true',
228 help = 'Being called as part of the server startup')
229
230 opts, args = op.parse_args()
231 if args: op.error('no arguments permitted')
232 OS.chdir(opts.dir)
233 T._debug = opts.debug
234 return opts
235
236## Service table, for running manually.
237service_info = [('connect', VERSION, {
238 'passive': (1, None, '[OPTIONS] USER', cmd_passive),
239 'active': (1, 1, 'PEER', cmd_active),
240 'info': (1, 1, 'PEER', cmd_info),
241 'list': (0, 0, '', cmd_list)
242})]
243
244if __name__ == '__main__':
245 opts = parse_options()
246 T.runservices(opts.tripesock, service_info,
247 setup = setup,
248 daemon = opts.daemon)
249
250###----- That's all, folks --------------------------------------------------