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