#! @PYTHON@ ### -*-python-*- ### ### Service for establishing dynamic connections ### ### (c) 2006 Straylight/Edgeware ### ###----- Licensing notice --------------------------------------------------- ### ### This file is part of Trivial IP Encryption (TrIPE). ### ### TrIPE is free software; you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 2 of the License, or ### (at your option) any later version. ### ### TrIPE is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with TrIPE; if not, write to the Free Software Foundation, ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. VERSION = '@VERSION@' ###-------------------------------------------------------------------------- ### External dependencies. from optparse import OptionParser import tripe as T import os as OS import cdb as CDB import mLib as M from time import time S = T.svcmgr ###-------------------------------------------------------------------------- ### Main service machinery. _magic = ['_magic'] # An object distinct from all others class Peer (object): """Representation of a peer in the database.""" def __init__(me, peer, cdb = None): """ Create a new peer, named PEER. Information about the peer is read from the database CDB, or the default one given on the command-line. """ me.name = peer try: record = (cdb or CDB.init(opts.cdb))['P' + peer] except KeyError: raise T.TripeJobError('unknown-peer', peer) me.__dict__.update(M.URLDecode(record, semip = True)) def get(me, key, default = _magic): """ Get the information stashed under KEY from the peer's database record. If DEFAULT is given, then use it if the database doesn't contain the necessary information. If no DEFAULT is given, then report an error. """ attr = me.__dict__.get(key, default) if attr is _magic: raise T.TripeJobError('malformed-peer', me.name, 'missing-key', key) return attr def list(me): """ Iterate over the available keys in the peer's database record. """ return me.__dict__.iterkeys() def addpeer(peer, addr): """ Process a connect request from a new peer PEER on address ADDR. Any existing peer with this name is disconnected from the server. """ if peer.name in S.list(): S.kill(peer.name) try: booltrue = ['t', 'true', 'y', 'yes', 'on'] S.add(peer.name, tunnel = peer.get('tunnel', None), keepalive = peer.get('keepalive', None), key = peer.get('key', None), priv = peer.get('priv', None), mobile = peer.get('mobile', 'nil') in booltrue, cork = peer.get('cork', 'nil') in booltrue, *addr) except T.TripeError, exc: raise T.TripeJobError(*exc.args) def cmd_active(name): """ active NAME: Handle an active connection request for the peer called NAME. The appropriate address is read from the database automatically. """ peer = Peer(name) addr = peer.get('peer') if addr == 'PASSIVE': raise T.TripeJobError('passive-peer', name) addpeer(peer, M.split(addr, quotep = True)[0]) def cmd_list(): """ list: Report a list of the available active peers. """ cdb = CDB.init(opts.cdb) for key in cdb.keys(): if key.startswith('P') and Peer(key[1:]).get('peer', '') != 'PASSIVE': T.svcinfo(key[1:]) def cmd_info(name): """ info NAME: Report the database entries for the named peer. """ peer = Peer(name) items = list(peer.list()) items.sort() for i in items: T.svcinfo('%s=%s' % (i, peer.get(i))) ## Dictionary mapping challenges to waiting passive-connection coroutines. chalmap = {} def cmd_passive(*args): """ passive [OPTIONS] USER: Await the arrival of the named USER. Report a challenge; when (and if!) the server receives a greeting quoting this challenge, add the corresponding peer to the server. """ timeout = 30 op = T.OptParse(args, ['-timeout']) for opt in op: if opt == '-timeout': timeout = T.timespec(op.arg()) user, = op.rest(1, 1) try: peer = CDB.init(opts.cdb)['U' + user] except KeyError: raise T.TripeJobError('unknown-user', user) chal = S.getchal() cr = T.Coroutine.getcurrent() timer = M.SelTimer(time() + timeout, lambda: cr.switch(None)) try: T.svcinfo(chal) chalmap[chal] = cr addr = cr.parent.switch() if addr is None: raise T.TripeJobError('connect-timeout') addpeer(Peer(peer), addr) finally: del chalmap[chal] def notify(_, code, *rest): """ Watch for notifications. In particular, if a GREETing appears quoting a challenge in the chalmap then wake up the corresponding coroutine. """ if code != 'GREET': return chal = rest[0] addr = rest[1:] if chal in chalmap: chalmap[chal].switch(addr) ###-------------------------------------------------------------------------- ### Start up. def setup(): """ Service setup. Register the notification-watcher, and add the automatic active peers. """ S.handler['NOTE'] = notify S.watch('+n') if opts.startup: cdb = CDB.init(opts.cdb) try: autos = cdb['%AUTO'] except KeyError: autos = '' for name in M.split(autos)[0]: try: peer = Peer(name, cdb) addpeer(peer, M.split(peer.get('peer'), quotep = True)[0]) except T.TripeJobError, err: S.warn('connect', 'auto-add-failed', name, *err.args) def parse_options(): """ Parse the command-line options. Automatically changes directory to the requested configdir, and turns on debugging. Returns the options object. """ op = OptionParser(usage = '%prog [-a FILE] [-d DIR]', version = '%%prog %s' % VERSION) op.add_option('-a', '--admin-socket', metavar = 'FILE', dest = 'tripesock', default = T.tripesock, help = 'Select socket to connect to [default %default]') op.add_option('-d', '--directory', metavar = 'DIR', dest = 'dir', default = T.configdir, help = 'Select current diretory [default %default]') op.add_option('-p', '--peerdb', metavar = 'FILE', dest = 'cdb', default = T.peerdb, help = 'Select peers database [default %default]') op.add_option('--daemon', dest = 'daemon', default = False, action = 'store_true', help = 'Become a daemon after successful initialization') op.add_option('--debug', dest = 'debug', default = False, action = 'store_true', help = 'Emit debugging trace information') op.add_option('--startup', dest = 'startup', default = False, action = 'store_true', help = 'Being called as part of the server startup') opts, args = op.parse_args() if args: op.error('no arguments permitted') OS.chdir(opts.dir) T._debug = opts.debug return opts ## Service table, for running manually. service_info = [('connect', VERSION, { 'passive': (1, None, '[OPTIONS] USER', cmd_passive), 'active': (1, 1, 'PEER', cmd_active), 'info': (1, 1, 'PEER', cmd_info), 'list': (0, 0, '', cmd_list) })] if __name__ == '__main__': opts = parse_options() T.runservices(opts.tripesock, service_info, setup = setup, daemon = opts.daemon) ###----- That's all, folks --------------------------------------------------