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