chiark / gitweb /
Merge branch 'mdw/backoff'
[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:
6411163d 90 booltrue = ['t', 'true', 'y', 'yes', 'on']
a62f8e8a
MW
91 S.add(peer.name,
92 tunnel = peer.get('tunnel', None),
93 keepalive = peer.get('keepalive', None),
48b84569 94 key = peer.get('key', None),
6411163d
MW
95 mobile = peer.get('mobile', 'nil') in booltrue,
96 cork = peer.get('cork', 'nil') in booltrue,
a62f8e8a
MW
97 *addr)
98 except T.TripeError, exc:
99 raise T.TripeJobError(*exc.args)
100
101def cmd_active(name):
102 """
103 active NAME: Handle an active connection request for the peer called NAME.
104
105 The appropriate address is read from the database automatically.
106 """
107 peer = Peer(name)
108 addr = peer.get('peer')
109 if addr == 'PASSIVE':
110 raise T.TripeJobError('passive-peer', name)
111 addpeer(peer, M.split(addr, quotep = True)[0])
112
113def cmd_list():
114 """
115 list: Report a list of the available active peers.
116 """
117 cdb = CDB.init(opts.cdb)
118 for key in cdb.keys():
119 if key.startswith('P') and Peer(key[1:]).get('peer', '') != 'PASSIVE':
120 T.svcinfo(key[1:])
121
122def cmd_info(name):
123 """
124 info NAME: Report the database entries for the named peer.
125 """
126 peer = Peer(name)
127 items = list(peer.list())
128 items.sort()
129 for i in items:
130 T.svcinfo('%s=%s' % (i, peer.get(i)))
131
132## Dictionary mapping challenges to waiting passive-connection coroutines.
133chalmap = {}
134
135def cmd_passive(*args):
136 """
137 passive [OPTIONS] USER: Await the arrival of the named USER.
138
139 Report a challenge; when (and if!) the server receives a greeting quoting
140 this challenge, add the corresponding peer to the server.
141 """
142 timeout = 30
143 op = T.OptParse(args, ['-timeout'])
144 for opt in op:
145 if opt == '-timeout':
146 timeout = T.timespec(op.arg())
147 user, = op.rest(1, 1)
148 try:
149 peer = CDB.init(opts.cdb)['U' + user]
150 except KeyError:
151 raise T.TripeJobError('unknown-user', user)
152 chal = S.getchal()
153 cr = T.Coroutine.getcurrent()
154 timer = M.SelTimer(time() + timeout, lambda: cr.switch(None))
155 try:
156 T.svcinfo(chal)
157 chalmap[chal] = cr
158 addr = cr.parent.switch()
159 if addr is None:
160 raise T.TripeJobError('connect-timeout')
161 addpeer(Peer(peer), addr)
162 finally:
163 del chalmap[chal]
164
165def notify(_, code, *rest):
166 """
167 Watch for notifications.
168
169 In particular, if a GREETing appears quoting a challenge in the chalmap
170 then wake up the corresponding coroutine.
171 """
172 if code != 'GREET':
173 return
174 chal = rest[0]
175 addr = rest[1:]
176 if chal in chalmap:
177 chalmap[chal].switch(addr)
178
179###--------------------------------------------------------------------------
180### Start up.
181
182def setup():
183 """
184 Service setup.
185
186 Register the notification-watcher, and add the automatic active peers.
187 """
188 S.handler['NOTE'] = notify
189 S.watch('+n')
190 if opts.startup:
191 cdb = CDB.init(opts.cdb)
192 try:
193 autos = cdb['%AUTO']
194 except KeyError:
195 autos = ''
196 for name in M.split(autos)[0]:
197 try:
198 peer = Peer(name, cdb)
199 addpeer(peer, M.split(peer.get('peer'), quotep = True)[0])
200 except T.TripeJobError, err:
201 S.warn('connect', 'auto-add-failed', name, *err.args)
202
203def parse_options():
204 """
205 Parse the command-line options.
206
207 Automatically changes directory to the requested configdir, and turns on
208 debugging. Returns the options object.
209 """
210 op = OptionParser(usage = '%prog [-a FILE] [-d DIR]',
211 version = '%%prog %s' % VERSION)
212
213 op.add_option('-a', '--admin-socket',
214 metavar = 'FILE', dest = 'tripesock', default = T.tripesock,
215 help = 'Select socket to connect to [default %default]')
216 op.add_option('-d', '--directory',
217 metavar = 'DIR', dest = 'dir', default = T.configdir,
218 help = 'Select current diretory [default %default]')
219 op.add_option('-p', '--peerdb',
220 metavar = 'FILE', dest = 'cdb', default = T.peerdb,
221 help = 'Select peers database [default %default]')
222 op.add_option('--daemon', dest = 'daemon',
223 default = False, action = 'store_true',
224 help = 'Become a daemon after successful initialization')
225 op.add_option('--debug', dest = 'debug',
226 default = False, action = 'store_true',
227 help = 'Emit debugging trace information')
228 op.add_option('--startup', dest = 'startup',
229 default = False, action = 'store_true',
230 help = 'Being called as part of the server startup')
231
232 opts, args = op.parse_args()
233 if args: op.error('no arguments permitted')
234 OS.chdir(opts.dir)
235 T._debug = opts.debug
236 return opts
237
238## Service table, for running manually.
239service_info = [('connect', VERSION, {
240 'passive': (1, None, '[OPTIONS] USER', cmd_passive),
241 'active': (1, 1, 'PEER', cmd_active),
242 'info': (1, 1, 'PEER', cmd_info),
243 'list': (0, 0, '', cmd_list)
244})]
245
246if __name__ == '__main__':
247 opts = parse_options()
248 T.runservices(opts.tripesock, service_info,
249 setup = setup,
250 daemon = opts.daemon)
251
252###----- That's all, folks --------------------------------------------------