Index: fedd/Makefile
===================================================================
--- fedd/Makefile	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/Makefile	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -1,5 +1,8 @@
 
-GENERATED_MODS= fedd/fedd_services.py fedd/fedd_internal_services.py \
-		fedd/fedd_services_types.py
+MODULE_DIR=federation
+
+GENERATED_MODS= ${MODULE_DIR}/fedd_services.py \
+		${MODULE_DIR}/fedd_internal_services.py \
+		${MODULE_DIR}/fedd_services_types.py
 WSDL_FILES= fedd.wsdl fedd_bindings.wsdl fedd_messages.wsdl fedd_types.xsd \
 	    fedd_internal.wsdl fedd_internal_bindings.wsdl \
@@ -14,14 +17,14 @@
 
 ${GENERATED_MODS}:	${WSDL_FILES}
-	wsdl2py --file fedd.wsdl --complexType --output-dir=fedd
+	wsdl2py --file fedd.wsdl --complexType --output-dir=${MODULE_DIR}
 	wsdl2py --file fedd_internal.wsdl --complexType \
-		--types=fedd_services_types --output-dir=fedd
+		--types=fedd_services_types --output-dir=${MODULE_DIR}
 
 clean:
-	rm -f ${GENERATED_MODS} fedd/*.pyc *.pyc
+	rm -f ${GENERATED_MODS} ${MODULE_DIR}/*.pyc *.pyc
 	rm -rf build dist fedd.egg-info MANIFEST
 
 dist:	${DISTFILES}
 
-dist/fedd-1.0.tar.gz: all
+dist/fedd-${VERSION}.tar.gz: all
 	python ./setup.py sdist
Index: fedd/exp_access_db.py
===================================================================
--- fedd/exp_access_db.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/exp_access_db.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -6,5 +6,5 @@
 from optparse import OptionParser
 
-from fedid import fedid
+from federation import fedid
 
 class opt_parser(OptionParser):
Index: fedd/fedd.py
===================================================================
--- fedd/fedd.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/fedd.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -5,8 +5,8 @@
 from optparse import OptionParser
 
-from fedd import config_parser
-from fedd.server import server, xmlrpc_handler, soap_handler
-from fedd.util import fedd_ssl_context
-from fedd.deter_impl import new_feddservice
+from federation import config_parser
+from federation.server import server, xmlrpc_handler, soap_handler
+from federation.util import fedd_ssl_context
+from federation.deter_impl import new_feddservice
 
 from threading import Lock, Thread
Index: fedd/fedd/__init__.py
===================================================================
--- fedd/fedd/__init__.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,7 +1,0 @@
-#/usr/local/bin/python
-
-from fedid import fedid
-from service_error import service_error
-from config_parser import config_parser
-
-import parse_detail
Index: fedd/fedd/access.py
===================================================================
--- fedd/fedd/access.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,784 +1,0 @@
-#!/usr/local/bin/python
-
-import os,sys
-import re
-import string
-import copy
-import pickle
-import logging
-
-from threading import *
-
-from util import *
-from allocate_project import allocate_project_local, allocate_project_remote
-from access_project import access_project
-from fedid import fedid, generate_fedid
-from authorizer import authorizer
-from service_error import service_error
-from remote_service import xmlrpc_handler, soap_handler, service_caller
-
-
-# Make log messages disappear if noone configures a fedd logger
-class nullHandler(logging.Handler):
-    def emit(self, record): pass
-
-fl = logging.getLogger("fedd.access")
-fl.addHandler(nullHandler())
-
-class access:
-    """
-    The implementation of access control based on mapping users to projects.
-
-    Users can be mapped to existing projects or have projects created
-    dynamically.  This implements both direct requests and proxies.
-    """
-
-    class parse_error(RuntimeError): pass
-
-    bool_attrs = ("project_priority", "allow_proxy")
-    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
-    id_attrs = ("testbed", "proxy",
-	    "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
-	    "project_allocation_uri", "project_allocation_cert_file", 
-	    "project_allocation_cert_pwd", "project_allocation_trusted_certs")
-    id_list_attrs = ("restricted",)
-
-    proxy_RequestAccess= service_caller('RequestAccess')
-    proxy_ReleaseAccess= service_caller('ReleaseAccess')
-
-    def __init__(self, config=None, auth=None):
-	"""
-	Initializer.  Pulls parameters out of the ConfigParser's access section.
-	"""
-
-	# Make sure that the configuration is in place
-	if config: 
-	    if not config.has_section("access"):
-		config.add_section("access")
-	    if not config.has_section("globals"):
-		config.add_section("globals")
-	else:
-	    raise RunTimeError("No config to fedd.access")
-
-	# Create instance attributes from the static lists
-	for a in access.bool_attrs:
-	    if config.has_option("access", a):
-		setattr(self, a, config.get("access", a))
-	    else:
-		setattr(self, a, False)
-
-	for a in access.emulab_attrs + access.id_attrs:
-	    if config.has_option("access", a):
-		setattr(self, a, config.get("access",a))
-	    else:
-		setattr(self, a, None)
-
-	self.attrs = { }
-	self.access = { }
-	self.restricted = [ ]
-	self.fedid_category = { }
-	self.projects = { }
-	self.keys = { }
-	self.allocation = { }
-	self.state = { 
-	    'projects': self.projects,
-	    'allocation' : self.allocation,
-	    'keys' : self.keys
-	}
-	self.log = logging.getLogger("fedd.access")
-	set_log_level(config, "access", self.log)
-	self.state_lock = Lock()
-
-	if auth: self.auth = auth
-	else:
-	    self.log.error(\
-		    "[access]: No authorizer initialized, creating local one.")
-	    auth = authorizer()
-
-	self.fedid_default = "testbed"
-	if config.has_option("access", "accessdb"):
-	    self.read_access(config.get("access", "accessdb"))
-
-	self.state_filename = config.get("access", "access_state", "")
-	self.read_state()
-	# Certs are promoted from the generic to the specific, so without a
-	# specific proxy certificate, the main certificates are used for
-	# proxy interactions. If no dynamic project certificates, then
-	# proxy certs are used, and if none of those the main certs.
-
-	if config.has_option("globals", "proxy_cert_file"):
-	    if not self.project_allocation_cert_file:
-		self.project_allocation_cert_file = \
-			config.get("globals", "proxy_cert_file")
-		if config.has_option("globals", "proxy_cert_pwd"):
-		    self.project_allocation_cert_pwd = \
-			    config.get("globals", "proxy_cert_pwd")
-
-	if config.has_option("globals", "proxy_trusted_certs"):
-	    if not self.project_allocation_trusted_certs:
-		self.project_allocation_trusted_certs =\
-			config.get("globals", proxy_trusted_certs)
-
-	if config.has_option("globals", "cert_file"):
-	    has_pwd = config.has_option("globals", "cert_pwd")
-	    if not self.project_allocation_cert_file:
-		self.project_allocation_cert_file = \
-			config.get("globals", "cert_file")
-		if has_pwd: 
-		    self.project_allocation_cert_pwd = \
-			    config.get("globals", "cert_pwd")
-	    if not self.proxy_cert_file:
-		self.proxy_cert_file = config.get("globals", "cert_file")
-		if has_pwd:
-		    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
-
-	if config.get("globals", "trusted_certs"):
-	    if not self.proxy_trusted_certs:
-		self.proxy_trusted_certs = \
-			config.get("globals", "trusted_certs")
-	    if not self.project_allocation_trusted_certs:
-		self.project_allocation_trusted_certs = \
-			config.get("globals", "trusted_certs")
-
-	proj_certs = (self.project_allocation_cert_file, 
-		self.project_allocation_trusted_certs,
-		self.project_allocation_cert_pwd)
-
-	self.soap_services = {\
-	    'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
-	    'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
-	    }
-	self.xmlrpc_services =  {\
-	    'RequestAccess': xmlrpc_handler('RequestAccess',
-		self.RequestAccess),
-	    'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
-		self.ReleaseAccess),
-	    }
-
-
-	if not config.has_option("allocate", "uri"):
-	    self.allocate_project = \
-		allocate_project_local(config, auth)
-	else:
-	    self.allocate_project = \
-		allocate_project_remote(config, auth)
-
-	# If the project allocator exports services, put them in this object's
-	# maps so that classes that instantiate this can call the services.
-	self.soap_services.update(self.allocate_project.soap_services)
-	self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
-
-
-    def read_access(self, config):
-	"""
-	Read a configuration file and set internal parameters.
-
-	The format is more complex than one might hope.  The basic format is
-	attribute value pairs separated by colons(:) on a signle line.  The
-	attributes in bool_attrs, emulab_attrs and id_attrs can all be set
-	directly using the name: value syntax.  E.g. 
-	boss: hostname
-	sets self.boss to hostname.  In addition, there are access lines of the
-	form (tb, proj, user) -> (aproj, auser) that map the first tuple of
-	names to the second for access purposes.  Names in the key (left side)
-	can include "<NONE> or <ANY>" to act as wildcards or to require the
-	fields to be empty.  Similarly aproj or auser can be <SAME> or
-	<DYNAMIC> indicating that either the matching key is to be used or a
-	dynamic user or project will be created.  These names can also be
-	federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
-	can be followed with a colon-separated list of node types to which that
-	project has access (or will have access if dynamic).
-	Testbed attributes outside the forms above can be given using the
-	format attribute: name value: value.  The name is a single word and the
-	value continues to the end of the line.  Empty lines and lines startin
-	with a # are ignored.
-
-	Parsing errors result in a self.parse_error exception being raised.
-	"""
-	lineno=0
-	name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
-	fedid_expr = "fedid:[" + string.hexdigits + "]+"
-	key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
-	access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
-		"<SAME>" + "(?::" + name_expr + ")*|" + \
-		fedid_expr + "(?::" + name_expr + ")*|" + \
-		name_expr + "(?::" + name_expr + ")*)"
-	access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
-
-	restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
-	attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
-		re.IGNORECASE)
-	access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
-		key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
-		access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
-
-	def parse_name(n):
-	    if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
-	    else: return n
-	
-	def auth_name(n):
-	    if isinstance(n, basestring):
-		if n =='<any>' or n =='<none>': return None
-		else: return unicode(n)
-	    else:
-		return n
-
-	f = open(config, "r");
-	for line in f:
-	    lineno += 1
-	    line = line.strip();
-	    if len(line) == 0 or line.startswith('#'):
-		continue
-
-	    # Extended (attribute: x value: y) attribute line
-	    m = attr_re.match(line)
-	    if m != None:
-		attr, val = m.group(1,2)
-		self.attrs[attr] = val
-		continue
-
-	    # Restricted entry 
-	    m = restricted_re.match(line)
-	    if m != None:
-		val = m.group(1)
-		self.restricted.append(val)
-		continue
-
-	    # Access line (t, p, u) -> (ap, cu, su) line
-	    m = access_re.match(line)
-	    if m != None:
-		access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
-		auth_key = tuple([ auth_name(x) for x in access_key])
-		aps = m.group(4).split(":");
-		if aps[0] == 'fedid:':
-		    del aps[0]
-		    aps[0] = fedid(hexstr=aps[0])
-
-		cu = parse_name(m.group(5))
-		su = parse_name(m.group(6))
-
-		access_val = (access_project(aps[0], aps[1:]),
-			parse_name(m.group(5)), parse_name(m.group(6)))
-
-		self.access[access_key] = access_val
-		self.auth.set_attribute(auth_key, "access")
-		continue
-
-	    # Nothing matched to here: unknown line - raise exception
-	    f.close()
-	    raise self.parse_error("Unknown statement at line %d of %s" % \
-		    (lineno, config))
-	f.close()
-
-
-    def dump_state(self):
-	"""
-	Dump the state read from a configuration file.  Mostly for debugging.
-	"""
-	for a in access.bool_attrs:
-	    print "%s: %s" % (a, getattr(self, a ))
-	for a in access.emulab_attrs + access.id_attrs:
-	    print "%s: %s" % (a, getattr(self, a))
-	for k, v in self.attrs.iteritems():
-	    print "%s %s" % (k, v)
-	print "Access DB:"
-	for k, v in self.access.iteritems():
-	    print "%s %s" % (k, v)
-	print "Trust DB:"
-	for k, v in self.fedid_category.iteritems():
-	    print "%s %s" % (k, v)
-	print "Restricted: %s" % str(',').join(sorted(self.restricted))
-
-    def get_users(self, obj):
-	"""
-	Return a list of the IDs of the users in dict
-	"""
-	if obj.has_key('user'):
-	    return [ unpack_id(u['userID']) \
-		    for u in obj['user'] if u.has_key('userID') ]
-	else:
-	    return None
-
-    def write_state(self):
-	try:
-	    f = open(self.state_filename, 'w')
-	    pickle.dump(self.state, f)
-	except IOError, e:
-	    self.log.error("Can't write file %s: %s" % \
-		    (self.state_filename, e))
-	except pickle.PicklingError, e:
-	    self.log.error("Pickling problem: %s" % e)
-	except TypeError, e:
-	    self.log.error("Pickling problem (TypeError): %s" % e)
-
-
-    def read_state(self):
-	"""
-	Read a new copy of access state.  Old state is overwritten.
-
-	State format is a simple pickling of the state dictionary.
-	"""
-	try:
-	    f = open(self.state_filename, "r")
-	    self.state = pickle.load(f)
-
-	    self.allocation = self.state['allocation']
-	    self.projects = self.state['projects']
-	    self.keys = self.state['keys']
-
-	    self.log.debug("[read_state]: Read state from %s" % \
-		    self.state_filename)
-	except IOError, e:
-	    self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
-		    % (self.state_filename, e))
-	except EOFError, e:
-	    self.log.warning("[read_state]: Empty or damaged state file: %s:"\
-		    % self.state_filename)
-	except pickle.UnpicklingError, e:
-	    self.log.warning(("[read_state]: No saved state: " + \
-		    "Unpickling failed: %s") % e)
-
-	# Add the ownership attributes to the authorizer.  Note that the
-	# indices of the allocation dict are strings, but the attributes are
-	# fedids, so there is a conversion.
-	for k in self.allocation.keys():
-	    for o in self.allocation[k].get('owners', []):
-		self.auth.set_attribute(o, fedid(hexstr=k))
-
-
-    def permute_wildcards(self, a, p):
-	"""Return a copy of a with various fields wildcarded.
-
-	The bits of p control the wildcards.  A set bit is a wildcard
-	replacement with the lowest bit being user then project then testbed.
-	"""
-	if p & 1: user = ["<any>"]
-	else: user = a[2]
-	if p & 2: proj = "<any>"
-	else: proj = a[1]
-	if p & 4: tb = "<any>"
-	else: tb = a[0]
-
-	return (tb, proj, user)
-
-    def find_access(self, search):
-	"""
-	Search the access DB for a match on this tuple.  Return the matching
-	access tuple and the user that matched.
-	
-	NB, if the initial tuple fails to match we start inserting wildcards in
-	an order determined by self.project_priority.  Try the list of users in
-	order (when wildcarded, there's only one user in the list).
-	"""
-	if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
-	else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
-
-	for p in perm: 
-	    s = self.permute_wildcards(search, p)
-	    # s[2] is None on an anonymous, unwildcarded request
-	    if s[2] != None:
-		for u in s[2]:
-		    if self.access.has_key((s[0], s[1], u)):
-			return (self.access[(s[0], s[1], u)], u)
-	    else:
-		if self.access.has_key(s):
-		    return (self.access[s], None)
-	return None, None
-
-    def lookup_access(self, req, fid):
-	"""
-	Determine the allowed access for this request.  Return the access and
-	which fields are dynamic.
-
-	The fedid is needed to construct the request
-	"""
-	# Search keys
-	tb = None
-	project = None
-	user = None
-	# Return values
-	rp = access_project(None, ())
-	ru = None
-
-	if req.has_key('project'):
-	    p = req['project']
-	    if p.has_key('name'):
-		project = unpack_id(p['name'])
-	    user = self.get_users(p)
-	else:
-	    user = self.get_users(req)
-
-	user_fedids = [ u for u in user if isinstance(u, fedid)]
-	# Determine how the caller is representing itself.  If its fedid shows
-	# up as a project or a singleton user, let that stand.  If neither the
-	# usernames nor the project name is a fedid, the caller is a testbed.
-	if project and isinstance(project, fedid):
-	    if project == fid:
-		# The caller is the project (which is already in the tuple
-		# passed in to the authorizer)
-		owners = user_fedids
-		owners.append(project)
-	    else:
-		raise service_error(service_error.req,
-			"Project asserting different fedid")
-	else:
-	    if fid not in user_fedids:
-		tb = fid
-		owners = user_fedids
-		owners.append(fid)
-	    else:
-		if len(fedids) > 1:
-		    raise service_error(service_error.req,
-			    "User asserting different fedid")
-		else:
-		    # Which is a singleton
-		    owners = user_fedids
-	# Confirm authorization
-	for u in user:
-	    self.log.debug("[lookup_access] Checking access for %s" % \
-		    ((tb, project, u),))
-	    if self.auth.check_attribute((tb, project, u), 'access'):
-		self.log.debug("[lookup_access] Access granted")
-		break
-	    else:
-		self.log.debug("[lookup_access] Access Denied")
-	else:
-	    raise service_error(service_error.access, "Access denied")
-
-	# This maps a valid user to the Emulab projects and users to use
-	found, user_match = self.find_access((tb, project, user))
-	
-	if found == None:
-	    raise service_error(service_error.access,
-		    "Access denied - cannot map access")
-
-	# resolve <dynamic> and <same> in found
-	dyn_proj = False
-	dyn_create_user = False
-	dyn_service_user = False
-
-	if found[0].name == "<same>":
-	    if project != None:
-		rp.name = project
-	    else : 
-		raise service_error(\
-			service_error.server_config,
-			"Project matched <same> when no project given")
-	elif found[0].name == "<dynamic>":
-	    rp.name = None
-	    dyn_proj = True
-	else:
-	    rp.name = found[0].name
-	rp.node_types = found[0].node_types;
-
-	if found[1] == "<same>":
-	    if user_match == "<any>":
-		if user != None: rcu = user[0]
-		else: raise service_error(\
-			service_error.server_config,
-			"Matched <same> on anonymous request")
-	    else:
-		rcu = user_match
-	elif found[1] == "<dynamic>":
-	    rcu = None
-	    dyn_create_user = True
-	
-	if found[2] == "<same>":
-	    if user_match == "<any>":
-		if user != None: rsu = user[0]
-		else: raise service_error(\
-			service_error.server_config,
-			"Matched <same> on anonymous request")
-	    else:
-		rsu = user_match
-	elif found[2] == "<dynamic>":
-	    rsu = None
-	    dyn_service_user = True
-
-	return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
-		owners
-
-    def build_response(self, alloc_id, ap):
-	"""
-	Create the SOAP response.
-
-	Build the dictionary description of the response and use
-	fedd_utils.pack_soap to create the soap message.  ap is the allocate
-	project message returned from a remote project allocation (even if that
-	allocation was done locally).
-	"""
-	# Because alloc_id is already a fedd_services_types.IDType_Holder,
-	# there's no need to repack it
-	msg = { 
-		'allocID': alloc_id,
-		'emulab': { 
-		    'domain': self.domain,
-		    'boss': self.boss,
-		    'ops': self.ops,
-		    'fileServer': self.fileserver,
-		    'eventServer': self.eventserver,
-		    'project': ap['project']
-		},
-	    }
-	if len(self.attrs) > 0:
-	    msg['emulab']['fedAttr'] = \
-		[ { 'attribute': x, 'value' : y } \
-			for x,y in self.attrs.iteritems()]
-	return msg
-
-    def RequestAccess(self, req, fid):
-	"""
-	Handle the access request.  Proxy if not for us.
-
-	Parse out the fields and make the allocations or rejections if for us,
-	otherwise, assuming we're willing to proxy, proxy the request out.
-	"""
-
-	# The dance to get into the request body
-	if req.has_key('RequestAccessRequestBody'):
-	    req = req['RequestAccessRequestBody']
-	else:
-	    raise service_error(service_error.req, "No request!?")
-
-	if req.has_key('destinationTestbed'):
-	    dt = unpack_id(req['destinationTestbed'])
-
-	if dt == None or dt == self.testbed:
-	    # Request for this fedd
-	    found, dyn, owners = self.lookup_access(req, fid)
-	    restricted = None
-	    ap = None
-
-	    # If this is a request to export a project and the access project
-	    # is not the project to export, access denied.
-	    if req.has_key('exportProject'):
-		ep = unpack_id(req['exportProject'])
-		if ep != found[0].name:
-		    raise service_error(service_error.access,
-			    "Cannot export %s" % ep)
-
-	    # Check for access to restricted nodes
-	    if req.has_key('resources') and req['resources'].has_key('node'):
-		resources = req['resources']
-		restricted = [ t for n in resources['node'] \
-				if n.has_key('hardware') \
-				    for t in n['hardware'] \
-					if t in self.restricted ]
-		inaccessible = [ t for t in restricted \
-				    if t not in found[0].node_types]
-		if len(inaccessible) > 0:
-		    raise service_error(service_error.access,
-			    "Access denied (nodetypes %s)" % \
-			    str(', ').join(inaccessible))
-	    # These collect the keys for the two roles into single sets, one
-	    # for creation and one for service.  The sets are a simple way to
-	    # eliminate duplicates
-	    create_ssh = set([ x['sshPubkey'] \
-		    for x in req['createAccess'] \
-			if x.has_key('sshPubkey')])
-
-	    service_ssh = set([ x['sshPubkey'] \
-		    for x in req['serviceAccess'] \
-			if x.has_key('sshPubkey')])
-
-	    if len(create_ssh) > 0 and len(service_ssh) >0: 
-		if dyn[1]: 
-		    # Compose the dynamic project request 
-		    # (only dynamic, dynamic currently allowed)
-		    preq = { 'AllocateProjectRequestBody': \
-				{ 'project' : {\
-				    'user': [ \
-				    { \
-					'access': [ { 'sshPubkey': s } \
-					    for s in service_ssh ], 
-					 'role': "serviceAccess",\
-				    }, \
-				    { \
-					'access': [ { 'sshPubkey': s } \
-					    for s in create_ssh ], 
-					 'role': "experimentCreation",\
-				    }, \
-				    ], \
-				    }\
-				}\
-			    }
-		    if restricted != None and len(restricted) > 0:
-			preq['AllocateProjectRequestBody']['resources'] = \
-			    [ {'node': { 'hardware' :  [ h ] } } \
-				    for h in restricted ]
-		    ap = self.allocate_project.dynamic_project(preq)
-		else:
-		    preq = {'StaticProjectRequestBody' : \
-			    { 'project': \
-				{ 'name' : { 'localname' : found[0].name },\
-				  'user' : [ \
-				    {\
-					'userID': { 'localname' : found[1] }, \
-					'access': [ { 'sshPubkey': s } 
-					    for s in create_ssh ],
-					'role': 'experimentCreation'\
-				    },\
-				    {\
-					'userID': { 'localname' : found[2] }, \
-					'access': [ { 'sshPubkey': s } 
-					    for s in service_ssh ],
-					'role': 'serviceAccess'\
-				    },\
-				]}\
-			    }\
-		    }
-		    if restricted != None and len(restricted) > 0:
-			preq['StaticProjectRequestBody']['resources'] = \
-			    [ {'node': { 'hardware' :  [ h ] } } \
-				    for h in restricted ]
-		    ap = self.allocate_project.static_project(preq)
-	    else:
-		raise service_error(service_error.req, 
-			"SSH access parameters required")
-	    # keep track of what's been added
-	    allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
-	    aid = unicode(allocID)
-
-	    self.state_lock.acquire()
-	    self.allocation[aid] = { }
-	    if dyn[1]:
-		try:
-		    pname = ap['project']['name']['localname']
-		except KeyError:
-		    self.state_lock.release()
-		    raise service_error(service_error.internal,
-			    "Misformed allocation response?")
-		if self.projects.has_key(pname): self.projects[pname] += 1
-		else: self.projects[pname] = 1
-		self.allocation[aid]['project'] = pname
-
-	    self.allocation[aid]['keys'] = [ ]
-
-	    try:
-		for u in ap['project']['user']:
-		    uname = u['userID']['localname']
-		    for k in [ k['sshPubkey'] for k in u['access'] \
-			    if k.has_key('sshPubkey') ]:
-			kv = "%s:%s" % (uname, k)
-			if self.keys.has_key(kv): self.keys[kv] += 1
-			else: self.keys[kv] = 1
-			self.allocation[aid]['keys'].append((uname, k))
-	    except KeyError:
-		self.state_lock.release()
-		raise service_error(service_error.internal,
-			"Misformed allocation response?")
-
-	    self.allocation[aid]['owners'] = owners
-	    self.write_state()
-	    self.state_lock.release()
-	    for o in owners:
-		self.auth.set_attribute(o, allocID)
-	    resp = self.build_response({ 'fedid': allocID } , ap)
-	    return resp
-	else:
-	    if self.allow_proxy:
-		resp = self.proxy_RequestAccess.call_service(dt, req,
-			    self.proxy_cert_file, self.proxy_cert_pwd,
-			    self.proxy_trusted_certs)
-		if resp.has_key('RequestAccessResponseBody'):
-		    return resp['RequestAccessResponseBody']
-		else:
-		    return None
-	    else:
-		raise service_error(service_error.access,
-			"Access proxying denied")
-
-    def ReleaseAccess(self, req, fid):
-	# The dance to get into the request body
-	if req.has_key('ReleaseAccessRequestBody'):
-	    req = req['ReleaseAccessRequestBody']
-	else:
-	    raise service_error(service_error.req, "No request!?")
-
-	if req.has_key('destinationTestbed'):
-	    dt = unpack_id(req['destinationTestbed'])
-	else:
-	    dt = None
-
-	if dt == None or dt == self.testbed:
-	    # Local request
-	    try:
-		if req['allocID'].has_key('localname'):
-		    auth_attr = aid = req['allocID']['localname']
-		elif req['allocID'].has_key('fedid'):
-		    aid = unicode(req['allocID']['fedid'])
-		    auth_attr = req['allocID']['fedid']
-		else:
-		    raise service_error(service_error.req,
-			    "Only localnames and fedids are understood")
-	    except KeyError:
-		raise service_error(service_error.req, "Badly formed request")
-
-	    if not self.auth.check_attribute(fid, auth_attr):
-		raise service_error(service_error.access, "Access Denied")
-
-	    # If we know this allocation, reduce the reference counts and
-	    # remove the local allocations.  Otherwise report an error.  If
-	    # there is an allocation to delete, del_users will be a dictonary
-	    # of sets where the key is the user that owns the keys in the set.
-	    # We use a set to avoid duplicates.  del_project is just the name
-	    # of any dynamic project to delete.  We're somewhat lazy about
-	    # deleting authorization attributes.  Having access to something
-	    # that doesn't exist isn't harmful.
-	    del_users = { }
-	    del_project = None
-	    if self.allocation.has_key(aid):
-		self.state_lock.acquire()
-		for k in self.allocation[aid]['keys']:
-		    kk = "%s:%s" % k
-		    self.keys[kk] -= 1
-		    if self.keys[kk] == 0:
-			if not del_users.has_key(k[0]):
-			    del_users[k[0]] = set()
-			del_users[k[0]].add(k[1])
-			del self.keys[kk]
-
-		if self.allocation[aid].has_key('project'):
-		    pname = self.allocation[aid]['project']
-		    self.projects[pname] -= 1
-		    if self.projects[pname] == 0:
-			del_project = pname
-			del self.projects[pname]
-
-		del self.allocation[aid]
-		self.write_state()
-		self.state_lock.release()
-		# If we actually have resources to deallocate, prepare the call.
-		if del_project or del_users:
-		    msg = { 'project': { }}
-		    if del_project:
-			msg['project']['name']= {'localname': del_project}
-		    users = [ ]
-		    for u in del_users.keys():
-			users.append({ 'userID': { 'localname': u },\
-			    'access' :  \
-				    [ {'sshPubkey' : s } for s in del_users[u]]\
-			})
-		    if users: 
-			msg['project']['user'] = users
-		    if self.allocate_project.release_project:
-			msg = { 'ReleaseProjectRequestBody' : msg}
-			self.allocate_project.release_project(msg)
-		return { 'allocID': req['allocID'] } 
-	    else:
-		raise service_error(service_error.req, "No such allocation")
-
-	else:
-	    if self.allow_proxy:
-		resp = self.proxy_ReleaseAccess.call_service(dt, req,
-			    self.proxy_cert_file, self.proxy_cert_pwd,
-			    self.proxy_trusted_certs)
-		if resp.has_key('ReleaseAccessResponseBody'):
-		    return resp['ReleaseAccessResponseBody']
-		else:
-		    return None
-	    else:
-		raise service_error(service_error.access,
-			"Access proxying denied")
-
-
Index: fedd/fedd/access_project.py
===================================================================
--- fedd/fedd/access_project.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,20 +1,0 @@
-#!/usr/local/bin/python
-
-class access_project:
-    """
-    A project description used to grant access to this testbed.
-
-    The description includes a name and a list of node types to which the
-    project will be granted access.
-    """
-    def __init__(self, name, nt):
-	self.name = name
-	self.node_types = list(nt)
-
-    def __repr__(self):
-	if len(self.node_types) > 0:
-	    return "access_proj('%s', ['%s'])" % \
-		    (self.name, str("','").join(self.node_types))
-	else:
-	    return "access_proj('%s', [])" % self.name
-
Index: fedd/fedd/allocate_project.py
===================================================================
--- fedd/fedd/allocate_project.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,517 +1,0 @@
-#!/usr/local/bin/python
-
-import os,sys
-import re
-import random
-import string
-import subprocess
-import tempfile
-
-from util import *
-from fedid import fedid
-from fixed_resource import read_key_db, read_project_db
-from remote_service import xmlrpc_handler, soap_handler, service_caller
-from service_error import service_error
-import logging
-
-
-# Configure loggers to dump to /dev/null which avoids errors if calling classes
-# don't configure them.
-class nullHandler(logging.Handler):
-    def emit(self, record): pass
-
-fl = logging.getLogger("fedd.allocate.local")
-fl.addHandler(nullHandler())
-fl = logging.getLogger("fedd.allocate.remote")
-fl.addHandler(nullHandler())
-
-
-class allocate_project_local:
-    """
-    Allocate projects on this machine in response to an access request.
-    """
-    dynamic_projects = 4
-    dynamic_keys= 2
-    confirm_keys = 1
-    none = 0
-
-    levels = {
-	    'dynamic_projects': dynamic_projects,
-	    'dynamic_keys': dynamic_keys,
-	    'confirm_keys': confirm_keys,
-	    'none': none,
-    }
-
-    def __init__(self, config, auth=None):
-	"""
-	Initializer.  Parses a configuration if one is given.
-	"""
-
-	self.debug = config.get("allocate", "debug", False)
-	self.wap = config.get('allocate', 'wap', '/usr/testbed/sbin/wap')
-	self.newproj = config.get('allocate', 'newproj',
-		'/usr/testbed/sbin/newproj')
-	self.mkproj = config.get('allocate', 'mkproj', 
-		'/usr/testbed/sbin/mkproj')
-	self.rmproj = config.get('allocate', 'rmproj',
-		'/usr/testbed/sbin/rmproj')
-	self.addpubkey = config.get('allocate', 'addpubkey', 
-		'/usr/testbed/sbin/taddpubkey')
-	self.grantnodetype = config.get('allocate', 'grantnodetype', 
-		'/usr/testbed/sbin/grantnodetype')
-	self.confirmkey = config.get('allocate', 'confirmkey', 
-		'/usr/testbed/sbin/taddpubkey')
-	self.allocation_level = config.get("allocate", "allocation_level", 
-		"none")
-	self.log = logging.getLogger("fedd.allocate.local")
-	set_log_level(config, "allocate", self.log)
-
-	if auth:
-	    self.auth = auth
-	else:
-	    auth = authorizer()
-	    log.warn("[allocate] No authorizer passed in, using local one")
-
-	try:
-	    self.allocation_level = \
-		    self.levels[self.allocation_level.strip().lower()]
-	except KeyError:
-	    self.log.error("Bad allocation_level %s.  Defaulting to none" % \
-		    self.allocation_error)
-	    self.allocation_level = self.none
-
-	access_db = config.get("allocate", "accessdb")
-	if access_db:
-	    try:
-		read_simple_accessdb(access_db, self.auth, 'allocate')
-	    except IOError, e:
-		raise service_error(service_error.internal,
-			"Error reading accessDB %s: %s" % (access_db, e))
-	    except ValueError:
-		raise service_error(service_error.internal, "%s" % e)
-
-
-	fixed_key_db = config.get("allocate", "fixed_keys", None)
-	fixed_project_db = config.get("allocate", "fixed_projects", None)
-	self.fixed_keys = set()
-	self.fixed_projects = set()
-
-	# initialize the fixed resource sets
-	for db, rset, fcn in (\
-		(fixed_key_db, self.fixed_keys, read_key_db), \
-		(fixed_project_db, self.fixed_projects, read_project_db)):
-	    if db:
-		try:
-		    rset.update(fcn(db))
-		except:
-		    self.log.debug("Can't read resources from %s" % db)
-	
-	# Internal services are SOAP only
-	self.soap_services = {\
-		"AllocateProject": soap_handler("AllocateProject", 
-		    self.dynamic_project),
-		"StaticProject": soap_handler("StaticProject", 
-		    self.static_project),
-		"ReleaseProject": soap_handler("ReleaseProject", 
-		    self.release_project),
-		}
-	self.xmlrpc_services = { }
-
-    def random_string(self, s, n=3):
-	"""Append n random ASCII characters to s and return the string"""
-	rv = s
-	for i in range(0,n):
-	    rv += random.choice(string.ascii_letters)
-	return rv
-
-    def write_attr_xml(self, file, root, lines):
-	"""
-	Write an emulab config file for a dynamic project.
-
-	Format is <root><attribute name=lines[0]>lines[1]</attribute></root>
-	"""
-	# Convert a pair to an attribute line
-	out_attr = lambda a,v : \
-		'<attribute name="%s"><value>%s</value></attribute>' % (a, v)
-
-	f = os.fdopen(file, "w")
-	f.write("<%s>\n" % root)
-	f.write("\n".join([out_attr(*l) for l in lines]))
-	f.write("</%s>\n" % root)
-	f.close()
-
-
-    def dynamic_project(self, req, fedid=None):
-	"""
-	Create a dynamic project with ssh access
-
-	Req includes the project and resources as a dictionary
-	"""
-
-	# Internal calls do not have a fedid parameter (i.e., local calls on
-	# behalf of already vetted fedids)
-	if fedid and not self.auth.check_attribute(fedid, "allocate"):
-	    self.log.debug("[allocate] Access denied (%s)" % fedid)
-	    raise service_error(service_error.access, "Access Denied")
-
-	if self.allocation_level < self.dynamic_projects:
-	    raise service_error(service_error.access, 
-		    "[dynamic_project] dynamic project allocation not " + \
-			    "permitted: check allocation level")
-	# tempfiles for the parameter files
-	uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
-		dir="/tmp")
-	pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
-		dir="/tmp")
-
-	if req.has_key('AllocateProjectRequestBody') and \
-		req['AllocateProjectRequestBody'].has_key('project'):
-	    proj = req['AllocateProjectRequestBody']['project']
-	else:
-	    raise service_error(service_error.req, 
-		    "Badly formed allocation request")
-	# Take the first user and ssh key
-	name = proj.get('name', None) or self.random_string("proj",4)
-	user = proj.get('user', None)
-	if user != None:
-	    user = user[0]	# User is a list, take the first entry
-	    if not user.has_key("userID"):
-		uname = self.random_string("user", 3)
-	    else:
-		uid = proj['userID']
-		# XXX: fedid
-		uname = uid.get('localname', None) or \
-			uid.get('kerberosUsername', None) or \
-			uid.get('uri', None)
-		if uname == None:
-		    raise service_error(service_error.req, "No ID for user");
-
-	    access = user.get('access', None)
-	    if access != None:
-		ssh = access[0].get('sshPubkey', None)
-		if ssh == None:
-		    raise service_error(service_error.req, 
-			    "No ssh key for user");
-	else:
-	    raise service_error(service_error.req, 
-		    "No access information for project");
-
-	# uname, name and ssh are set
-	user_fields = [
-		("name", "Federation User %s" % uname),
-		("email", "%s-fed@isi.deterlab.net" % uname),
-		("password", self.random_string("", 8)),
-		("login", uname),
-		("address", "4676 Admiralty"),
-		("city", "Marina del Rey"),
-		("state", "CA"),
-		("zip", "90292"),
-		("country", "USA"),
-		("phone", "310-448-9190"),
-		("title", "None"),
-		("affiliation", "USC/ISI"),
-		("pubkey", ssh)
-	]
-
-	proj_fields = [
-		("name", name),
-		("short description", "dynamic federated project"),
-		("URL", "http://www.isi.edu/~faber"),
-		("funders", "USC/USU"),
-		("long description", "Federation access control"),
-		("public", "1"),
-		("num_pcs", "100"),
-		("linkedtous", "1"),
-		("newuser_xml", userfile)
-	]
-	
-
-	# Write out the files
-	self.write_attr_xml(uf, "user", user_fields)
-	self.write_attr_xml(pf, "project", proj_fields)
-
-	# Generate the commands (only grantnodetype's are dynamic)
-	cmds = [
-		(self.wap, self.newproj, projfile),
-		(self.wap, self.mkproj, name)
-		]
-
-	# Add commands to grant access to any resources in the request.
-	for nt in [ h for r in req.get('resources', []) \
-		if r.has_key('node') and r['node'].has_key('hardware')\
-		    for h in r['node']['hardware'] ] :
-	    cmds.append((self.wap, self.grantnodetype, '-p', name, nt))
-
-	# Create the projects
-	rc = 0
-	for cmd in cmds:
-	    self.log.debug("[dynamic_project]: %s" % ' '.join(cmd))
-	    if not self.debug:
-		try:
-		    rc = subprocess.call(cmd)
-		except OSerror, e:
-		    raise service_error(service_error.internal,
-			    "Dynamic project subprocess creation error "+ \
-				    "[%s] (%s)" %  (cmd[1], e.strerror))
-
-	    if rc != 0: 
-		raise service_error(service_error.internal,
-			"Dynamic project subprocess error " +\
-				"[%s] (%d)" % (cmd[1], rc))
-	# Clean up tempfiles
-	os.unlink(userfile)
-	os.unlink(projfile)
-	rv = {\
-	    'project': {\
-		'name': { 'localname': name }, 
-		'user' : [ {\
-		    'userID': { 'localname' : uname },
-		    'access': [ { 'sshPubkey' : ssh } ],
-		} ]\
-	    }\
-	}
-	return rv
-
-    def static_project(self, req, fedid=None):
-	"""
-	Be certain that the local project in the request has access to the
-	proper resources and users have correct keys.  Add them if necessary.
-	"""
-
-	cmds =  []
-
-	# Internal calls do not have a fedid parameter (i.e., local calls on
-	# behalf of already vetted fedids)
-	if fedid and not self.auth.check_attribute(fedid, "allocate"):
-	    self.log.debug("[allocate] Access denied (%s)" % fedid)
-	    raise service_error(service_error.access, "Access Denied")
-	# While we should be more careful about this, for the short term, add
-	# the keys to the specified users.
-
-	try:
-	    users = req['StaticProjectRequestBody']['project']['user']
-	    pname = req['StaticProjectRequestBody']['project']\
-		    ['name']['localname']
-	    resources = req['StaticProjectRequestBody'].get('resources', [])
-	except KeyError:
-	    raise service_error(service_error.req, "Badly formed request")
-
-
-	for u in users:
-	    try:
-		name = u['userID']['localname']
-	    except KeyError:
-		raise service_error(service_error.req, "Badly formed user")
-	    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
-		    if k.has_key('sshPubkey')]:
-		if self.allocation_level >= self.dynamic_keys:
-		    cmds.append((self.wap, self.addpubkey, '-w', \
-			    '-u', name, '-k', sk))
-		elif self.allocation_level >= self.confirm_keys:
-		    cmds.append((self.wap, self.confirmkey, '-C', \
-			    '-u', name, '-k', sk))
-		else:
-		    self.log.warning("[static_project] no checking of " + \
-			    "static keys")
-	
-
-	# Add commands to grant access to any resources in the request.  The
-	# list comprehension pulls out the hardware types in the node entries
-	# in the resources list.
-	for nt in [ h for r in resources \
-		if r.has_key('node') and r['node'].has_key('hardware')\
-		    for h in r['node']['hardware'] ] :
-	    if self.allocation_level >= self.confirm_keys:
-		cmds.append((self.wap, self.grantnodetype, '-p', pname, nt))
-
-	# Run the commands 
-	rc = 0
-	for cmd in cmds:
-	    self.log.debug("[static_project]: %s" % ' '.join(cmd))
-	    if not self.debug:
-		try:
-		    rc = subprocess.call(cmd)
-		except OSError, e:
-		    raise service_error(service_error.internal,
-			    "Static project subprocess creation error "+ \
-				    "[%s] (%s)" %  (cmd[0], e.strerror))
-
-	    if rc != 0: 
-		raise service_error(service_error.internal,
-			"Static project subprocess error " +\
-				"[%s] (%d)" % (cmd[0], rc))
-
-	return { 'project': req['StaticProjectRequestBody']['project']}
-
-    def release_project(self, req, fedid=None):
-	"""
-	Remove user keys from users and delete dynamic projects.
-
-	Only keys not in the set of fixed keys are deleted. and there are
-	similar protections for projects.
-	"""
-	# Internal calls do not have a fedid parameter (i.e., local calls on
-	# behalf of already vetted fedids)
-	if fedid and not self.auth.check_attribute(fedid, "allocate"):
-	    self.log.debug("[allocate] Access denied (%s)" % fedid)
-	    raise service_error(service_error.access, "Access Denied")
-
-	cmds = []
-	pname = None
-	users = []
-
-	try:
-	    if req['ReleaseProjectRequestBody']['project'].has_key('name'):
-		pname = req['ReleaseProjectRequestBody']['project']\
-			['name']['localname']
-	    if req['ReleaseProjectRequestBody']['project'].has_key('user'):
-		users = req['ReleaseProjectRequestBody']['project']['user']
-	except KeyError:
-	    raise service_error(service_error.req, "Badly formed request")
-
-	for u in users:
-	    try:
-		name = u['userID']['localname']
-	    except KeyError:
-		raise service_error(service_error.req, "Badly formed user")
-	    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
-		    if k.has_key('sshPubkey')]:
-		if (name.rstrip(), sk.rstrip()) not in self.fixed_keys:
-		    if self.allocation_level >= self.dynamic_keys:
-			cmds.append((self.wap, self.addpubkey, '-R', '-w', \
-				'-u', name, '-k', sk))
-	if pname and pname not in self.fixed_projects and \
-		self.allocation_level >= self.dynamic_projects:
-	    cmds.append((self.wap, self.rmproj, pname))
-
-	# Run the commands 
-	rc = 0
-	for cmd in cmds:
-	    self.log.debug("[release_project]: %s" % ' '.join(cmd))
-	    if not self.debug:
-		try:
-		    rc = subprocess.call(cmd)
-		except OSError, e:
-		    raise service_error(service_error.internal,
-			    "Release project subprocess creation error "+ \
-				    "[%s] (%s)" %  (cmd[0], e.strerror))
-
-	    if rc != 0: 
-		raise service_error(service_error.internal,
-			"Release project subprocess error " +\
-				"[%s] (%d)" % (cmd[0], rc))
-
-	return { 'project': req['ReleaseProjectRequestBody']['project']}
-
-class allocate_project_remote:
-    """
-    Allocate projects on a remote machine using the internal SOAP interface
-    """
-    class proxy(service_caller):
-	"""
-	This class is a proxy functor (callable) that has the same signature as
-	a function called by soap_handler or xmlrpc_handler, but that used the
-	service_caller class to call the function remotely.
-	"""
-
-	def __init__(self, url, cert_file, cert_pwd, trusted_certs, auth, 
-		method):
-	    service_caller.__init__(self, method)
-	    self.url = url
-	    self.cert_file = cert_file
-	    self.cert_pwd = cert_pwd
-	    self.trusted_certs = trusted_certs
-	    self.request_body__name = "%sRequestBody" % method
-	    self.resp_name = "%sResponseBody" % method
-	    self.auth = auth
-	    # Calling the proxy object directly invokes the proxy_call method,
-	    # not the service_call method.
-	    self.__call__ = self.proxy_call
-	    
-
-	# Define the proxy, NB, the parameters to make_proxy are visible to the
-	# definition of proxy.
-	def proxy_call(self, req, fid=None):
-	    """
-	    Send req on to a remote project instantiator.
-
-	    Req is just the message to be sent.  This function re-wraps it.
-	    It also rethrows any faults.
-	    """
-
-	    if req.has_key(self.request_body_name):
-		req = req[self.request_body_name]
-	    else:
-		print "request error"
-		raise service_error(service_error.req, "Bad formated request");
-
-	    r = self.call_service(self.url, req, self.cert_file, self.cert_pwd,
-		    self.trusted_certs)
-	    if r.has_key(self.resp_name):
-		return r[self.resp_name]
-	    else:
-		print "response error"
-		raise service_error(service_error.protocol, 
-			"Bad proxy response")
-
-    # back to defining the allocate_project_remote class
-    def __init__(self, config, auth=None):
-	"""
-	Initializer.  Parses a configuration if one is given.
-	"""
-
-	self.debug = config.get("allocate", "debug", False)
-	self.url = config.get("allocate", "uri", "")
-
-	self.cert_file = config.get("allocate", "cert_file", None)
-	self.cert_pwd = config.get("allocate", "cert_pwd", None)
-	self.trusted_certs = config.get("allocate", "trusted_certs", None)
-
-	# Certs are promoted from the generic to the specific, so without a if
-	# no dynamic project certificates, then proxy certs are used, and if
-	# none of those the main certs.
-
-	if config.has_option("globals", "proxy_cert_file"):
-	    if not self.cert_file:
-		self.cert_file = config.get("globals", "proxy_cert_file")
-		if config.has_option("globals", "proxy_cert_pwd"):
-		    self.cert_pwd = config.get("globals", "proxy_cert_pwd")
-
-	if config.has_option("globals", "proxy_trusted_certs") and \
-		not self.trusted_certs:
-		self.trusted_certs = \
-			config.get("globals", "proxy_trusted_certs")
-
-	if config.has_option("globals", "cert_file"):
-	    has_pwd = config.has_option("globals", "cert_pwd")
-	    if not self.cert_file:
-		self.cert_file = config.get("globals", "cert_file")
-		if has_pwd: 
-		    self.cert_pwd = config.get("globals", "cert_pwd")
-
-	if config.get("globals", "trusted_certs") and not self.trusted_certs:
-		self.trusted_certs = \
-			config.get("globals", "trusted_certs")
-
-	self.soap_services = { }
-	self.xmlrpc_services = { }
-	self.log = logging.getLogger("fedd.allocate.remote")
-	set_log_level(config, "allocate", self.log)
-
-	if auth:
-	    self.auth = auth
-	else:
-	    auth = authorizer()
-	    log.warn("[allocate] No authorizer passed in, using local one")
-
-	# The specializations of the proxy functions
-	self.dynamic_project = self.proxy(self.url, self.cert_file, 
-		self.cert_pwd, self.trusted_certs, self.auth, 
-		"AllocateProject")
-	self.static_project = self.proxy(self.url, self.cert_file, 
-		self.cert_pwd, self.trusted_certs, self.auth, 
-		"StaticProject")
-	self.release_project = self.proxy(self.url, self.cert_file,
-		self.cert_pwd, self.trusted_certs, self.auth, 
-		"ReleaseProject")
-
Index: fedd/fedd/asn1_raw.py
===================================================================
--- fedd/fedd/asn1_raw.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,160 +1,0 @@
-# Modified from an example in the Python ASN.1 package to read certificate
-# files and extract key bits.  More recent versions of m2crypto support this
-# more directly.
-
-import sys, string, base64
-from pyasn1.type import tag,namedtype,namedval,univ,constraint,char,useful
-from pyasn1.codec.der import decoder, encoder
-from pyasn1 import error
-
-from M2Crypto import EVP
-
-# Would be autogenerated from ASN.1 source by a ASN.1 parser
-# X.509 spec (rfc2459)
-
-MAX = 64  # XXX ?
-
-class DirectoryString(univ.Choice):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
-        namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
-        namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
-        namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
-        namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
-        namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
-        )
-
-class AttributeValue(DirectoryString): pass
-
-class AttributeType(univ.ObjectIdentifier): pass
-
-class AttributeTypeAndValue(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('type', AttributeType()),
-        namedtype.NamedType('value', AttributeValue())
-        )
-
-class RelativeDistinguishedName(univ.SetOf):
-    componentType = AttributeTypeAndValue()
-
-class RDNSequence(univ.SequenceOf):
-    componentType = RelativeDistinguishedName()
-
-class Name(univ.Choice):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('', RDNSequence())
-        )
-                          
-class AlgorithmIdentifier(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
-        namedtype.OptionalNamedType('parameters', univ.Null())
-        # XXX syntax screwed?
-#        namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
-        )
-
-class Extension(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('extnID', univ.ObjectIdentifier()),
-        namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
-        namedtype.NamedType('extnValue', univ.OctetString())
-        )
-
-class Extensions(univ.SequenceOf):
-    componentType = Extension()
-    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
-
-class SubjectPublicKeyInfo(univ.Sequence):
-     componentType = namedtype.NamedTypes(
-         namedtype.NamedType('algorithm', AlgorithmIdentifier()),
-         namedtype.NamedType('subjectPublicKey', univ.BitString())
-         )
-
-class UniqueIdentifier(univ.BitString): pass
-
-class Time(univ.Choice):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('utcTime', useful.UTCTime()),
-        namedtype.NamedType('generalTime', useful.GeneralizedTime())
-        )
-    
-class Validity(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('notBefore', Time()),
-        namedtype.NamedType('notAfter', Time())
-        )
-
-class CertificateSerialNumber(univ.Integer): pass
-
-class Version(univ.Integer):
-    namedValues = namedval.NamedValues(
-        ('v1', 0), ('v2', 1), ('v3', 2)
-        )
-
-class TBSCertificate(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
-        namedtype.NamedType('serialNumber', CertificateSerialNumber()),
-        namedtype.NamedType('signature', AlgorithmIdentifier()),
-        namedtype.NamedType('issuer', Name()),
-        namedtype.NamedType('validity', Validity()),
-        namedtype.NamedType('subject', Name()),
-        namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
-        namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
-        namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
-        namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
-        )
-
-class Certificate(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('tbsCertificate', TBSCertificate()),
-        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
-        namedtype.NamedType('signatureValue', univ.BitString())
-        )
-
-# end of ASN.1 data structures
-
-
-def get_bits(substrate):
-    certType = Certificate()
-    cert = decoder.decode(substrate, asn1Spec=certType)[0]
-    n = cert
-    for name in ('tbsCertificate', 'subjectPublicKeyInfo', 
-	    'subjectPublicKey'):
-	n = n.getComponentByName(name)
-	if n == None: raise RuntimeException("Bad cert")
-
-    b = []
-    for i in range(0, len(n), 8):
-	v = 0
-	for j in range(0, 8):
-	    v = (v << 1) + n[i+j]
-	b.append(v)
-
-    return str().join([chr(x) for x in b])
-
-
-def get_key_bits_from_file(fname):
-    stSpam, stHam, stDump = 0, 1, 2
-    state = stSpam
-
-    f = open(fname)
-
-    for certLine in f:
-	certLine = string.strip(certLine)
-	if state == stSpam:
-	    if certLine == '-----BEGIN CERTIFICATE-----':
-		substrate = ""
-		state = stHam
-		continue
-	if state == stHam:
-	    if certLine == '-----END CERTIFICATE-----':
-		state = stDump
-	    else:
-		substrate = substrate + base64.b64decode(certLine)
-	if state == stDump:
-	    f.close()
-	    return get_bits(substrate)
-
-def get_key_bits_from_cert(cert):
-    return get_bits(cert.as_der())
Index: fedd/fedd/authorizer.py
===================================================================
--- fedd/fedd/authorizer.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,125 +1,0 @@
-#/usr/local/bin/python
-
-from fedid import fedid
-
-class authorizer:
-    """
-    This class keeps track of authorization attributes for the various modules
-    running.  When it gets smarter it will be the basis for a real
-    attribute-based authentication system.
-    """
-    # general error exception for badly formed names.  
-    class bad_name(RuntimeError): pass
-
-    def __init__(self, def_attr="testbed"):
-	self.attrs = { }
-	self.globals=set()
-
-    @staticmethod
-    def auth_name(name):
-	"""
-	Helper to convert a non-unicode local name to a unicode string.  Mixed
-	representations can needlessly confuse the authorizer.
-	"""
-	if isinstance(name, basestring):
-	    if not isinstance(name, unicode): return unicode(name)
-	    else: return name
-	else: return name
-
-    def valid_name(self, name):
-	"""
-	Ensure that the given name is valid.  A valid name can either be a
-	triple of strings and fedids representing one of our generalized Emulab
-	names or a single fedid.  Compound names can include wildcards (None)
-	and must anchor to a fedid at their highest level (unless they're all
-	None)
-
-	This either returns True or throws an exception.  More an assertion
-	than a function.
-	"""
-	if isinstance(name, tuple) and len(name) == 3:
-	    for n in name:
-		if n: 
-		    if not (isinstance(n, basestring) or isinstance(n, fedid)):
-			raise self.bad_name("names must be either a " +\
-				"triple or a fedid")
-	    for n in name: 
-		if n:
-		    if isinstance(n, fedid):
-			return True
-		    else:
-			raise self.bad_name("Compound names must be " + \
-				"rooted in fedids: %s" % str(name))
-
-	    return True
-	elif isinstance(name, fedid):
-	    return True
-	else:
-	    raise self.bad_name("Names must be a triple or a fedid (%s)" % name)
-
-    def set_attribute(self, name, attr):
-	"""
-	Attach attr to name.  Multiple attrs can be attached.
-	"""
-	self.valid_name(name)
-	if isinstance(name, tuple):
-	    aname = tuple([ self.auth_name(n) for n in name])
-	else:
-	    aname = self.auth_name(name)
-
-	if not self.attrs.has_key(aname):
-	    self.attrs[aname] = set()
-	self.attrs[aname].add(attr)
-
-    def unset_attribute(self, name, attr):
-	"""
-	Remove an attribute from name
-	"""
-	self.valid_name(name)
-	if isinstance(name, tuple):
-	    aname = tuple([ self.auth_name(n) for n in name])
-	else:
-	    aname = self.auth_name(name)
-
-	attrs = self.attrs.get(aname, None)
-	if attrs: attrs.discard(attr)
-
-    def check_attribute(self, name, attr):
-	"""
-	Return True if name has attr (or if attr is global).  Tuple names match
-	any tuple name that matches all names present and has None entries in
-	other fileds.  For tuple names True implies that there is a matching
-	tuple name with the attribute.
-	"""
-	def tup(tup, i, p):
-	    mask = 1 << i
-	    if p & mask : return authorizer.auth_name(tup[i])
-	    else: return None
-
-	self.valid_name(name)
-	if attr in self.globals:
-	    return True
-
-	if isinstance(name, tuple):
-	    for p in range(0,8):
-		lookup = ( tup(name, 0, p), tup(name,1, p), tup(name,2,p))
-		if self.attrs.has_key(lookup):
-		    if attr in self.attrs[lookup]:
-			return True
-	else:
-	    return  attr in self.attrs.get(self.auth_name(name), set())
-
-    def set_global_attribute(self, attr):
-	"""
-	Set a global attribute.  All names, even those otherwise unknown to the
-	authorizer have this attribute.
-	"""
-	self.globals.add(attr)
-
-    def unset_global_attribute(self, attr):
-	"""
-	Remove a global attribute
-	"""
-
-	self.globals.discard(attr)
-
Index: fedd/fedd/config_parser.py
===================================================================
--- fedd/fedd/config_parser.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,45 +1,0 @@
-#!/usr/local/bin/python
-
-from ConfigParser import *
-
-class config_parser(SafeConfigParser):
-    """
-    A SafeConfig parser with a more forgiving get attribute
-    """
-
-    def safe_get(self, sect, opt, method, default=None):
-	"""
-	If the option is present, return it, otherwise the default.
-	"""
-	if self.has_option(sect, opt): return method(self, sect, opt)
-	else: return default
-
-    def get(self, sect, opt, default=None):
-	"""
-	This returns the requested option or a given default.
-
-	It's more like getattr than get.
-	"""
-
-	return self.safe_get(sect, opt, SafeConfigParser.get, default)
-
-    def getint(self, sect, opt, default=0):
-	"""
-	Returns the selected option as an int or a default.
-	"""
-
-	return self.safe_get(sect, opt, SafeConfigParser.getint, default)
-
-    def getfloat(self, sect, opt, default=0.0):
-	"""
-	Returns the selected option as an int or a default.
-	"""
-
-	return self.safe_get(sect, opt, SafeConfigParser.getfloat, default)
-
-    def getboolean(self, sect, opt, default=False):
-	"""
-	Returns the selected option as a boolean or a default.
-	"""
-
-	return self.safe_get(sect, opt, SafeConfigParser.getboolean, default)
Index: fedd/fedd/deter_impl.py
===================================================================
--- fedd/fedd/deter_impl.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,63 +1,0 @@
-#!/usr/local/bin/python
-
-from access import access
-from experiment_control import experiment_control_local
-from split import split_local
-from util import read_simple_accessdb
-
-from authorizer import authorizer
-
-class deter_impl:
-    """
-    The implementation of access control based on mapping users to projects.
-
-    Users can be mapped to existing projects or have projects created
-    dynamically.  This implements both direct requests and proxies.
-    """
-    # Used by the SOAP caller
-    soap_namespaces = ('http://www.isi.edu/faber/fedd.wsdl',
-	    'http://www.isi.edu/faber/fedd_internal.wsdl')
-
-    def __init__(self, config=None):
-	"""
-	Initializer.  Uses the parsed configuration to create appropriate
-	components.
-	"""
-	self.soap_services = { }
-	self.xmlrpc_services = { }
-	self.auth = authorizer()
-
-	if config:
-	    self.cert_file = config.get("globals", "cert_file");
-	    self.cert_pwd = config.get("globals", "cert_pwd");
-	    self.trusted_certs = config.get("globals", "trusted_certs");
-
-	    access_db = config.get("globals", "accessdb")
-
-	    if access_db:
-		try:
-		    read_simple_accessdb(access_db, self.auth)
-		except IOError, e:
-		    raise service_error(service_error.internal,
-			    "Error reading accessDB %s: %s" % (access_db, e))
-		except ValueError:
-		    raise service_error(service_error.internal, "%s" % e)
-
-	    if config.has_section("access"):
-		self.access = access(config, self.auth)
-		self.soap_services.update(self.access.soap_services) 
-		self.xmlrpc_services.update(self.access.xmlrpc_services) 
-
-	    if config.has_section("experiment_control"):
-		self.experiment = \
-			experiment_control_local(config, self.auth)
-		self.soap_services.update(self.experiment.soap_services) 
-		self.xmlrpc_services.update(self.experiment.xmlrpc_services) 
-
-	    if config.has_section("splitter"):
-		self.splitter = split_local(config, self.auth)
-		self.soap_services.update(self.splitter.soap_services) 
-		self.xmlrpc_services.update(self.splitter.xmlrpc_services) 
-
-def new_feddservice(config):
-    return deter_impl(config)
Index: fedd/fedd/experiment_control.py
===================================================================
--- fedd/fedd/experiment_control.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,2005 +1,0 @@
-#!/usr/local/bin/python
-
-import os,sys
-
-import re
-import random
-import string
-import subprocess
-import tempfile
-import copy
-import pickle
-import logging
-
-import traceback
-# For parsing visualization output and splitter output
-import xml.parsers.expat
-
-from threading import *
-from subprocess import *
-
-from util import *
-from fedid import fedid, generate_fedid
-from remote_service import xmlrpc_handler, soap_handler, service_caller
-from service_error import service_error
-
-
-class nullHandler(logging.Handler):
-    def emit(self, record): pass
-
-fl = logging.getLogger("fedd.experiment_control")
-fl.addHandler(nullHandler())
-
-class experiment_control_local:
-    """
-    Control of experiments that this system can directly access.
-
-    Includes experiment creation, termination and information dissemination.
-    Thred safe.
-    """
-    
-    class thread_pool:
-	"""
-	A class to keep track of a set of threads all invoked for the same
-	task.  Manages the mutual exclusion of the states.
-	"""
-	def __init__(self):
-	    """
-	    Start a pool.
-	    """
-	    self.changed = Condition()
-	    self.started = 0
-	    self.terminated = 0
-
-	def acquire(self):
-	    """
-	    Get the pool's lock.
-	    """
-	    self.changed.acquire()
-
-	def release(self):
-	    """
-	    Release the pool's lock.
-	    """
-	    self.changed.release()
-
-	def wait(self, timeout = None):
-	    """
-	    Wait for a pool thread to start or stop.
-	    """
-	    self.changed.wait(timeout)
-
-	def start(self):
-	    """
-	    Called by a pool thread to report starting.
-	    """
-	    self.changed.acquire()
-	    self.started += 1
-	    self.changed.notifyAll()
-	    self.changed.release()
-
-	def terminate(self):
-	    """
-	    Called by a pool thread to report finishing.
-	    """
-	    self.changed.acquire()
-	    self.terminated += 1
-	    self.changed.notifyAll()
-	    self.changed.release()
-
-	def clear(self):
-	    """
-	    Clear all pool data.
-	    """
-	    self.changed.acquire()
-	    self.started = 0
-	    self.terminated =0
-	    self.changed.notifyAll()
-	    self.changed.release()
-
-    class pooled_thread(Thread):
-	"""
-	One of a set of threads dedicated to a specific task.  Uses the
-	thread_pool class above for coordination.
-	"""
-	def __init__(self, group=None, target=None, name=None, args=(), 
-		kwargs={}, pdata=None, trace_file=None):
-	    Thread.__init__(self, group, target, name, args, kwargs)
-	    self.rv = None	    # Return value of the ops in this thread
-	    self.exception = None   # Exception that terminated this thread
-	    self.target=target	    # Target function to run on start()
-	    self.args = args	    # Args to pass to target
-	    self.kwargs = kwargs    # Additional kw args
-	    self.pdata = pdata	    # thread_pool for this class
-	    # Logger for this thread
-	    self.log = logging.getLogger("fedd.experiment_control")
-	
-	def run(self):
-	    """
-	    Emulate Thread.run, except add pool data manipulation and error
-	    logging.
-	    """
-	    if self.pdata:
-		self.pdata.start()
-
-	    if self.target:
-		try:
-		    self.rv = self.target(*self.args, **self.kwargs)
-		except service_error, s:
-		    self.exception = s
-		    self.log.error("Thread exception: %s %s" % \
-			    (s.code_string(), s.desc))
-		except:
-		    self.exception = sys.exc_info()[1]
-		    self.log.error(("Unexpected thread exception: %s" +\
-			    "Trace %s") % (self.exception,\
-				traceback.format_exc()))
-	    if self.pdata:
-		self.pdata.terminate()
-
-    call_RequestAccess = service_caller('RequestAccess')
-    call_ReleaseAccess = service_caller('ReleaseAccess')
-    call_Ns2Split = service_caller('Ns2Split')
-
-    def __init__(self, config=None, auth=None):
-	"""
-	Intialize the various attributes, most from the config object
-	"""
-	self.thread_with_rv = experiment_control_local.pooled_thread
-	self.thread_pool = experiment_control_local.thread_pool
-
-	self.cert_file = None
-	self.cert_pwd = None
-	self.trusted_certs = None
-
-	# Walk through the various relevant certificat specifying config
-	# attributes until the local certificate attributes can be resolved.
-	# The walk is from most specific to most general specification.
-	for s in ("experiment_control", "globals"):
-	    if config.has_section(s): 
-		if config.has_option(s, "cert_file"):
-		    if not self.cert_file:
-			self.cert_file = config.get(s, "cert_file")
-			self.cert_pwd = config.get(s, "cert_pwd")
-
-		if config.has_option(s, "trusted_certs"):
-		    if not self.trusted_certs:
-			self.trusted_certs = config.get(s, "trusted_certs")
-
-
-	self.exp_stem = "fed-stem"
-	self.log = logging.getLogger("fedd.experiment_control")
-	set_log_level(config, "experiment_control", self.log)
-	self.muxmax = 2
-	self.nthreads = 2
-	self.randomize_experiments = False
-
-	self.scp_exec = "/usr/bin/scp"
-	self.splitter = None
-	self.ssh_exec="/usr/bin/ssh"
-	self.ssh_keygen = "/usr/bin/ssh-keygen"
-	self.ssh_identity_file = None
-
-
-	self.debug = config.getboolean("experiment_control", "create_debug")
-	self.state_filename = config.get("experiment_control", 
-		"experiment_state_file")
-	self.splitter_url = config.get("experiment_control", "splitter_url")
-	self.fedkit = config.get("experiment_control", "fedkit")
-	accessdb_file = config.get("experiment_control", "accessdb")
-
-	self.ssh_pubkey_file = config.get("experiment_control", 
-		"ssh_pubkey_file")
-	self.ssh_privkey_file = config.get("experiment_control",
-		"ssh_privkey_file")
-	# NB for internal master/slave ops, not experiment setup
-	self.ssh_type = config.get("experiment_control", "sshkeytype", "rsa")
-	self.state = { }
-	self.state_lock = Lock()
-	self.tclsh = "/usr/local/bin/otclsh"
-	self.tcl_splitter = "/usr/testbed/lib/ns2ir/parse.tcl"
-	mapdb_file = config.get("experiment_control", "mapdb")
-	self.trace_file = sys.stderr
-
-	self.def_expstart = \
-		"sudo -H /bin/sh /usr/local/federation/bin/federate.sh >& " +\
-		"/tmp/federate";
-	self.def_mexpstart = "sudo -H /usr/local/federation/bin/make_hosts " +\
-		"FEDDIR/hosts";
-	self.def_gwstart = \
-		"sudo -H /usr/local/federation/bin/fed-tun.pl -f GWCONF>& " +\
-		"/tmp/bridge.log";
-	self.def_mgwstart = \
-		"sudo -H /usr/local/federation/bin/fed-tun.pl -f GWCONF >& " +\
-		"/tmp/bridge.log";
-	self.def_gwimage = "FBSD61-TUNNEL2";
-	self.def_gwtype = "pc";
-
-	if auth:
-	    self.auth = auth
-	else:
-	    self.log.error(\
-		    "[access]: No authorizer initialized, creating local one.")
-	    auth = authorizer()
-
-
-	if self.ssh_pubkey_file:
-	    try:
-		f = open(self.ssh_pubkey_file, 'r')
-		self.ssh_pubkey = f.read()
-		f.close()
-	    except IOError:
-		raise service_error(service_error.internal,
-			"Cannot read sshpubkey")
-	else:
-	    raise service_error(service_error.internal, 
-		    "No SSH public key file?")
-
-	if not self.ssh_privkey_file:
-	    raise service_error(service_error.internal, 
-		    "No SSH public key file?")
-
-
-	if mapdb_file:
-	    self.read_mapdb(mapdb_file)
-	else:
-	    self.log.warn("[experiment_control] No testbed map, using defaults")
-	    self.tbmap = { 
-		    'deter':'https://users.isi.deterlab.net:23235',
-		    'emulab':'https://users.isi.deterlab.net:23236',
-		    'ucb':'https://users.isi.deterlab.net:23237',
-		    }
-
-	if accessdb_file:
-		self.read_accessdb(accessdb_file)
-	else:
-	    raise service_error(service_error.internal,
-		    "No accessdb specified in config")
-
-	# Grab saved state.  OK to do this w/o locking because it's read only
-	# and only one thread should be in existence that can see self.state at
-	# this point.
-	if self.state_filename:
-	    self.read_state()
-
-	# Dispatch tables
-	self.soap_services = {\
-		'Create': soap_handler('Create', self.create_experiment),
-		'Vtopo': soap_handler('Vtopo', self.get_vtopo),
-		'Vis': soap_handler('Vis', self.get_vis),
-		'Info': soap_handler('Info', self.get_info),
-		'Terminate': soap_handler('Terminate', 
-		    self.terminate_experiment),
-	}
-
-	self.xmlrpc_services = {\
-		'Create': xmlrpc_handler('Create', self.create_experiment),
-		'Vtopo': xmlrpc_handler('Vtopo', self.get_vtopo),
-		'Vis': xmlrpc_handler('Vis', self.get_vis),
-		'Info': xmlrpc_handler('Info', self.get_info),
-		'Terminate': xmlrpc_handler('Terminate',
-		    self.terminate_experiment),
-	}
-
-    def copy_file(self, src, dest, size=1024):
-	"""
-	Exceedingly simple file copy.
-	"""
-	s = open(src,'r')
-	d = open(dest, 'w')
-
-	buf = "x"
-	while buf != "":
-	    buf = s.read(size)
-	    d.write(buf)
-	s.close()
-	d.close()
-
-    # Call while holding self.state_lock
-    def write_state(self):
-	"""
-	Write a new copy of experiment state after copying the existing state
-	to a backup.
-
-	State format is a simple pickling of the state dictionary.
-	"""
-	if os.access(self.state_filename, os.W_OK):
-	    self.copy_file(self.state_filename, \
-		    "%s.bak" % self.state_filename)
-	try:
-	    f = open(self.state_filename, 'w')
-	    pickle.dump(self.state, f)
-	except IOError, e:
-	    self.log.error("Can't write file %s: %s" % \
-		    (self.state_filename, e))
-	except pickle.PicklingError, e:
-	    self.log.error("Pickling problem: %s" % e)
-	except TypeError, e:
-	    self.log.error("Pickling problem (TypeError): %s" % e)
-
-    # Call while holding self.state_lock
-    def read_state(self):
-	"""
-	Read a new copy of experiment state.  Old state is overwritten.
-
-	State format is a simple pickling of the state dictionary.
-	"""
-	try:
-	    f = open(self.state_filename, "r")
-	    self.state = pickle.load(f)
-	    self.log.debug("[read_state]: Read state from %s" % \
-		    self.state_filename)
-	except IOError, e:
-	    self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
-		    % (self.state_filename, e))
-	except pickle.UnpicklingError, e:
-	    self.log.warning(("[read_state]: No saved state: " + \
-		    "Unpickling failed: %s") % e)
-	
-	for k in self.state.keys():
-	    try:
-		# This list should only have one element in it, but phrasing it
-		# as a for loop doesn't cost much, really.  We have to find the
-		# fedid elements anyway.
-		for eid in [ f['fedid'] \
-			for f in self.state[k]['experimentID']\
-			    if f.has_key('fedid') ]:
-		    self.auth.set_attribute(self.state[k]['owner'], eid)
-	    except KeyError, e:
-		self.log.warning("[read_state]: State ownership or identity " +\
-			"misformatted in %s: %s" % (self.state_filename, e))
-
-
-    def read_accessdb(self, accessdb_file):
-	"""
-	Read the mapping from fedids that can create experiments to their name
-	in the 3-level access namespace.  All will be asserted from this
-	testbed and can include the local username and porject that will be
-	asserted on their behalf by this fedd.  Each fedid is also added to the
-	authorization system with the "create" attribute.
-	"""
-	self.accessdb = {}
-	# These are the regexps for parsing the db
-	name_expr = "[" + string.ascii_letters + string.digits + "\.\-]+"
-	project_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
-		"\s*->\(\s*("+name_expr+")\s*,\s*("+name_expr+")\s*\)\s*$")
-	user_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
-		"\s*->\s*(" + name_expr + ")\s*$")
-	lineno = 0
-
-	# Parse the mappings and store in self.authdb, a dict of 
-	# fedid -> (proj, user)
-	try:
-	    f = open(accessdb_file, "r")
-	    for line in f:
-		lineno += 1
-		line = line.strip()
-		if len(line) == 0 or line.startswith('#'):
-		    continue
-		m = project_line.match(line)
-		if m:
-		    fid = fedid(hexstr=m.group(1))
-		    project, user = m.group(2,3)
-		    if not self.accessdb.has_key(fid):
-			self.accessdb[fid] = []
-		    self.accessdb[fid].append((project, user))
-		    continue
-
-		m = user_line.match(line)
-		if m:
-		    fid = fedid(hexstr=m.group(1))
-		    project = None
-		    user = m.group(2)
-		    if not self.accessdb.has_key(fid):
-			self.accessdb[fid] = []
-		    self.accessdb[fid].append((project, user))
-		    continue
-		self.log.warn("[experiment_control] Error parsing access " +\
-			"db %s at line %d" %  (accessdb_file, lineno))
-	except IOError:
-	    raise service_error(service_error.internal,
-		    "Error opening/reading %s as experiment " +\
-			    "control accessdb" %  accessdb_file)
-	f.close()
-
-	# Initialize the authorization attributes
-	for fid in self.accessdb.keys():
-	    self.auth.set_attribute(fid, 'create')
-
-    def read_mapdb(self, file):
-	"""
-	Read a simple colon separated list of mappings for the
-	label-to-testbed-URL mappings.  Clears or creates self.tbmap.
-	"""
-
-	self.tbmap = { }
-	lineno =0
-	try:
-	    f = open(file, "r")
-	    for line in f:
-		lineno += 1
-		line = line.strip()
-		if line.startswith('#') or len(line) == 0:
-		    continue
-		try:
-		    label, url = line.split(':', 1)
-		    self.tbmap[label] = url
-		except ValueError, e:
-		    self.log.warn("[read_mapdb] Ignored bad line (%d) in " +\
-			    "map db: %s %s" % (lineno, line, e))
-	except IOError, e:
-	    self.log.warning("[read_mapdb]: No saved map database: Can't " +\
-		    "open %s: %s" % (file, e))
-	f.close()
-
-    def scp_file(self, file, user, host, dest=""):
-	"""
-	scp a file to the remote host.  If debug is set the action is only
-	logged.
-	"""
-
-	scp_cmd = [self.scp_exec, '-i', self.ssh_privkey_file, file, 
-		"%s@%s:%s" % (user, host, dest)]
-	rv = 0
-
-	try:
-	    dnull = open("/dev/null", "r")
-	except IOError:
-	    self.log.debug("[ssh_file]: failed to open /dev/null for redirect")
-	    dnull = Null
-
-	self.log.debug("[scp_file]: %s" % " ".join(scp_cmd))
-	if not self.debug:
-	    if dnull: rv = call(scp_cmd, stdout=dnull, stderr=dnull)
-	    else: rv = call(scp_cmd)
-
-	return rv == 0
-
-    def ssh_cmd(self, user, host, cmd, wname=None):
-	"""
-	Run a remote command on host as user.  If debug is set, the action is
-	only logged.
-	"""
-	sh_str = "%s -i %s %s@%s %s" % (self.ssh_exec, self.ssh_privkey_file, 
-		user, host, cmd)
-
-	try:
-	    dnull = open("/dev/null", "r")
-	except IOError:
-	    self.log.debug("[ssh_cmd]: failed to open /dev/null for redirect")
-	    dnull = Null
-
-	self.log.debug("[ssh_cmd]: %s" % sh_str)
-	if not self.debug:
-	    if dnull:
-		sub = Popen(sh_str, shell=True, stdout=dnull, stderr=dnull)
-	    else:
-		sub = Popen(sh_str, shell=True)
-	    return sub.wait() == 0
-	else:
-	    return True
-
-    def ship_configs(self, host, user, src_dir, dest_dir):
-	"""
-	Copy federant-specific configuration files to the federant.
-	"""
-	if not self.ssh_cmd(user, host, "mkdir -p %s" % dest_dir):
-	    return False
-	if not self.ssh_cmd(user, host, "chmod 770 %s" % dest_dir):
-	    return False
-
-	for f in os.listdir(src_dir):
-	    if os.path.isdir(f):
-		if not self.ship_configs(host, user, "%s/%s" % (src_dir, f), 
-			"%s/%s" % (dest_dir, f)):
-		    return False
-	    else:
-		if not self.scp_file("%s/%s" % (src_dir, f), 
-			user, host, dest_dir):
-		    return False
-	return True
-
-    def start_segment(self, tb, eid, tbparams, tmpdir, timeout=0):
-	"""
-	Start a sub-experiment on a federant.
-
-	Get the current state, modify or create as appropriate, ship data and
-	configs and start the experiment.  There are small ordering differences
-	based on the initial state of the sub-experiment.
-	"""
-	# ops node in the federant
-	host = "%s%s" % (tbparams[tb]['host'], tbparams[tb]['domain'])
-	user = tbparams[tb]['user']	# federant user
-	pid = tbparams[tb]['project']	# federant project
-	# XXX
-	base_confs = ( "hosts",)
-	tclfile = "%s.%s.tcl" % (eid, tb)   # sub-experiment description
-	# command to test experiment state
-	expinfo_exec = "/usr/testbed/bin/expinfo"  
-	# Configuration directories on the remote machine
-	proj_dir = "/proj/%s/exp/%s/tmp" % (pid, eid)
-	tarfiles_dir = "/proj/%s/tarfiles/%s" % (pid, eid)
-	rpms_dir = "/proj/%s/rpms/%s" % (pid, eid)
-	# Regular expressions to parse the expinfo response
-	state_re = re.compile("State:\s+(\w+)")
-	no_exp_re = re.compile("^No\s+such\s+experiment")
-	state = None	# Experiment state parsed from expinfo
-	# The expinfo ssh command
-	cmd = [self.ssh_exec, "%s@%s" % (user, host), expinfo_exec, pid, eid]
-
-	# Get status
-	self.log.debug("[start_segment]: %s"% " ".join(cmd))
-	dev_null = None
-	try:
-	    dev_null = open("/dev/null", "a")
-	except IOError, e:
-	    self.log.error("[start_segment]: can't open /dev/null: %s" %e)
-
-	if self.debug:
-	    state = 'swapped'
-	    rv = 0
-	else:
-	    status = Popen(cmd, stdout=PIPE, stderr=dev_null)
-	    for line in status.stdout:
-		m = state_re.match(line)
-		if m: state = m.group(1)
-		else:
-		    m = no_exp_re.match(line)
-		    if m: state = "none"
-	    rv = status.wait()
-
-	# If the experiment is not present the subcommand returns a non-zero
-	# return value.  If we successfully parsed a "none" outcome, ignore the
-	# return code.
-	if rv != 0 and state != "none":
-	    raise service_error(service_error.internal,
-		    "Cannot get status of segment %s:%s/%s" % (tb, pid, eid))
-
-	self.log.debug("[start_segment]: %s: %s" % (tb, state))
-	self.log.info("[start_segment]:transferring experiment to %s" % tb)
-
-	if not self.scp_file("%s/%s/%s" % (tmpdir, tb, tclfile), user, host):
-	    return False
-	# Clear the federation files
-	if not self.ssh_cmd(user, host, 
-		"/bin/sh -c \"'/bin/rm -rf %s'\"" % proj_dir):
-	    return False
-	if not self.ssh_cmd(user, host, 
-		"/bin/sh -c \"'mkdir -p %s'\"" % proj_dir):
-	    return False
-	# Clear and create the tarfiles and rpm directories
-	for d in (tarfiles_dir, rpms_dir):
-	    if not self.ssh_cmd(user, host, 
-		    "/bin/sh -c \"'/bin/rm -rf %s/*'\"" % d):
-		return False
-	    if not self.ssh_cmd(user, host, "mkdir -p %s" % d, 
-		    "create tarfiles"):
-		return False
-	
-	if state == 'active':
-	    # Remote experiment is active.  Modify it.
-	    for f in base_confs:
-		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
-			"%s/%s" % (proj_dir, f)):
-		    return False
-	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
-		    proj_dir):
-		return False
-	    if os.path.isdir("%s/tarfiles" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/tarfiles" % tmpdir, tarfiles_dir):
-		    return False
-	    if os.path.isdir("%s/rpms" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/rpms" % tmpdir, tarfiles_dir):
-		    return False
-	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
-	    if not self.ssh_cmd(user, host,
-		    "/usr/testbed/bin/modexp -r -s -w %s %s %s" % \
-			    (pid, eid, tclfile), "modexp"):
-		return False
-	    return True
-	elif state == "swapped":
-	    # Remote experiment swapped out.  Modify it and swap it in.
-	    for f in base_confs:
-		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
-			"%s/%s" % (proj_dir, f)):
-		    return False
-	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
-		    proj_dir):
-		return False
-	    if os.path.isdir("%s/tarfiles" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/tarfiles" % tmpdir, tarfiles_dir):
-		    return False
-	    if os.path.isdir("%s/rpms" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/rpms" % tmpdir, tarfiles_dir):
-		    return False
-	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
-	    if not self.ssh_cmd(user, host,
-		    "/usr/testbed/bin/modexp -w %s %s %s" % (pid, eid, tclfile),
-		    "modexp"):
-		return False
-	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
-	    if not self.ssh_cmd(user, host,
-		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
-		    "swapexp"):
-		return False
-	    return True
-	elif state == "none":
-	    # No remote experiment.  Create one.  We do this in 2 steps so we
-	    # can put the configuration files and scripts into the new
-	    # experiment directories.
-
-	    # Tarfiles must be present for creation to work
-	    if os.path.isdir("%s/tarfiles" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/tarfiles" % tmpdir, tarfiles_dir):
-		    return False
-	    if os.path.isdir("%s/rpms" % tmpdir):
-		if not self.ship_configs(host, user,
-			"%s/rpms" % tmpdir, tarfiles_dir):
-		    return False
-	    self.log.info("[start_segment]: Creating %s on %s" % (eid, tb))
-	    if not self.ssh_cmd(user, host,
-		    "/usr/testbed/bin/startexp -i -f -w -p %s -e %s %s" % \
-			    (pid, eid, tclfile), "startexp"):
-		return False
-	    # After startexp the per-experiment directories exist
-	    for f in base_confs:
-		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
-			"%s/%s" % (proj_dir, f)):
-		    return False
-	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
-		    proj_dir):
-		return False
-	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
-	    if not self.ssh_cmd(user, host,
-		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
-		    "swapexp"):
-		return False
-	    return True
-	else:
-	    self.log.debug("[start_segment]:unknown state %s" % state)
-	    return False
-
-    def stop_segment(self, tb, eid, tbparams):
-	"""
-	Stop a sub experiment by calling swapexp on the federant
-	"""
-	user = tbparams[tb]['user']
-	host = tbparams[tb]['host']
-	pid = tbparams[tb]['project']
-
-	self.log.info("[stop_segment]: Stopping %s on %s" % (eid, tb))
-	return self.ssh_cmd(user, host,
-		"/usr/testbed/bin/swapexp -w %s %s out" % (pid, eid))
-
-	
-    def generate_ssh_keys(self, dest, type="rsa" ):
-	"""
-	Generate a set of keys for the gateways to use to talk.
-
-	Keys are of type type and are stored in the required dest file.
-	"""
-	valid_types = ("rsa", "dsa")
-	t = type.lower();
-	if t not in valid_types: raise ValueError
-	cmd = [self.ssh_keygen, '-t', t, '-N', '', '-f', dest]
-
-	try:
-	    trace = open("/dev/null", "w")
-	except IOError:
-	    raise service_error(service_error.internal,
-		    "Cannot open /dev/null??");
-
-	# May raise CalledProcessError
-	self.log.debug("[generate_ssh_keys]: %s" % " ".join(cmd))
-	rv = call(cmd, stdout=trace, stderr=trace)
-	if rv != 0:
-	    raise service_error(service_error.internal, 
-		    "Cannot generate nonce ssh keys.  %s return code %d" \
-			    % (self.ssh_keygen, rv))
-
-    def gentopo(self, str):
-	"""
-	Generate the topology dtat structure from the splitter's XML
-	representation of it.
-
-	The topology XML looks like:
-	    <experiment>
-		<nodes>
-		    <node><vname></vname><ips>ip1:ip2</ips></node>
-		</nodes>
-		<lans>
-		    <lan>
-			<vname></vname><vnode></vnode><ip></ip>
-			<bandwidth></bandwidth><member>node:port</member>
-		    </lan>
-		</lans>
-	"""
-	class topo_parse:
-	    """
-	    Parse the topology XML and create the dats structure.
-	    """
-	    def __init__(self):
-		# Typing of the subelements for data conversion
-		self.str_subelements = ('vname', 'vnode', 'ips', 'ip', 'member')
-		self.int_subelements = ( 'bandwidth',)
-		self.float_subelements = ( 'delay',)
-		# The final data structure
-		self.nodes = [ ]
-		self.lans =  [ ]
-		self.topo = { \
-			'node': self.nodes,\
-			'lan' : self.lans,\
-		    }
-		self.element = { }  # Current element being created
-		self.chars = ""	    # Last text seen
-
-	    def end_element(self, name):
-		# After each sub element the contents is added to the current
-		# element or to the appropriate list.
-		if name == 'node':
-		    self.nodes.append(self.element)
-		    self.element = { }
-		elif name == 'lan':
-		    self.lans.append(self.element)
-		    self.element = { }
-		elif name in self.str_subelements:
-		    self.element[name] = self.chars
-		    self.chars = ""
-		elif name in self.int_subelements:
-		    self.element[name] = int(self.chars)
-		    self.chars = ""
-		elif name in self.float_subelements:
-		    self.element[name] = float(self.chars)
-		    self.chars = ""
-
-	    def found_chars(self, data):
-		self.chars += data.rstrip()
-
-
-	tp = topo_parse();
-	parser = xml.parsers.expat.ParserCreate()
-	parser.EndElementHandler = tp.end_element
-	parser.CharacterDataHandler = tp.found_chars
-
-	parser.Parse(str)
-
-	return tp.topo
-	
-
-    def genviz(self, topo):
-	"""
-	Generate the visualization the virtual topology
-	"""
-
-	neato = "/usr/local/bin/neato"
-	# These are used to parse neato output and to create the visualization
-	# file.
-	vis_re = re.compile('^\s*"?([\w\-]+)"?\s+\[.*pos="(\d+),(\d+)"')
-	vis_fmt = "<node><name>%s</name><x>%s</x><y>%s</y><type>" + \
-		"%s</type></node>"
-
-	try:
-	    # Node names
-	    nodes = [ n['vname'] for n in topo['node'] ]
-	    topo_lans = topo['lan']
-	except KeyError:
-	    raise service_error(service_error.internal, "Bad topology")
-
-	lans = { }
-	links = { }
-
-	# Walk through the virtual topology, organizing the connections into
-	# 2-node connections (links) and more-than-2-node connections (lans).
-	# When a lan is created, it's added to the list of nodes (there's a
-	# node in the visualization for the lan).
-	for l in topo_lans:
-	    if links.has_key(l['vname']):
-		if len(links[l['vname']]) < 2:
-		    links[l['vname']].append(l['vnode'])
-		else:
-		    nodes.append(l['vname'])
-		    lans[l['vname']] = links[l['vname']]
-		    del links[l['vname']]
-		    lans[l['vname']].append(l['vnode'])
-	    elif lans.has_key(l['vname']):
-		lans[l['vname']].append(l['vnode'])
-	    else:
-		links[l['vname']] = [ l['vnode'] ]
-
-
-	# Open up a temporary file for dot to turn into a visualization
-	try:
-	    df, dotname = tempfile.mkstemp()
-	    dotfile = os.fdopen(df, 'w')
-	except IOError:
-	    raise service_error(service_error.internal,
-		    "Failed to open file in genviz")
-
-	# Generate a dot/neato input file from the links, nodes and lans
-	try:
-	    print >>dotfile, "graph G {"
-	    for n in nodes:
-		print >>dotfile, '\t"%s"' % n
-	    for l in links.keys():
-		print >>dotfile, '\t"%s" -- "%s"' %  tuple(links[l])
-	    for l in lans.keys():
-		for n in lans[l]:
-		    print >>dotfile, '\t "%s" -- "%s"' % (n,l)
-	    print >>dotfile, "}"
-	    dotfile.close()
-	except TypeError:
-	    raise service_error(service_error.internal,
-		    "Single endpoint link in vtopo")
-	except IOError:
-	    raise service_error(service_error.internal, "Cannot write dot file")
-
-	# Use dot to create a visualization
-	dot = Popen([neato, '-Gstart=rand', '-Gepsilon=0.005', '-Gmaxiter=2000',
-		'-Gpack=true', dotname], stdout=PIPE)
-
-	# Translate dot to vis format
-	vis_nodes = [ ]
-	vis = { 'node': vis_nodes }
-	for line in dot.stdout:
-	    m = vis_re.match(line)
-	    if m:
-		vn = m.group(1)
-		vis_node = {'name': vn, \
-			'x': float(m.group(2)),\
-			'y' : float(m.group(3)),\
-		    }
-		if vn in links.keys() or vn in lans.keys():
-		    vis_node['type'] = 'lan'
-		else:
-		    vis_node['type'] = 'node'
-		vis_nodes.append(vis_node)
-	rv = dot.wait()
-
-	os.remove(dotname)
-	if rv == 0 : return vis
-	else: return None
-
-    def get_access(self, tb, nodes, user, tbparam, master, export_project,
-	    access_user):
-	"""
-	Get access to testbed through fedd and set the parameters for that tb
-	"""
-
-	translate_attr = {
-	    'slavenodestartcmd': 'expstart',
-	    'slaveconnectorstartcmd': 'gwstart',
-	    'masternodestartcmd': 'mexpstart',
-	    'masterconnectorstartcmd': 'mgwstart',
-	    'connectorimage': 'gwimage',
-	    'connectortype': 'gwtype',
-	    'tunnelcfg': 'tun',
-	    'smbshare': 'smbshare',
-	}
-
-	uri = self.tbmap.get(tb, None)
-	if not uri:
-	    raise service_error(serice_error.server_config, 
-		    "Unknown testbed: %s" % tb)
-
-	# currently this lumps all users into one service access group
-	service_keys = [ a for u in user \
-		for a in u.get('access', []) \
-		    if a.has_key('sshPubkey')]
-
-	if len(service_keys) == 0:
-	    raise service_error(service_error.req, 
-		    "Must have at least one SSH pubkey for services")
-
-
-	for p, u in access_user:
-	    self.log.debug(("[get_access] Attempting access from (%s, %s) " + \
-		    "to %s") %  ((p or "None"), u, uri))
-
-	    if p:
-		# Request with user and project specified
-		req = {\
-			'destinationTestbed' : { 'uri' : uri },
-			'project': { 
-			    'name': {'localname': p},
-			    'user': [ {'userID': { 'localname': u } } ],
-			    },
-			'user':  user,
-			'allocID' : { 'localname': 'test' },
-			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
-			'serviceAccess' : service_keys
-		    }
-	    else:
-		# Request with only user specified
-		req = {\
-			'destinationTestbed' : { 'uri' : uri },
-			'user':  [ {'userID': { 'localname': u } } ],
-			'allocID' : { 'localname': 'test' },
-			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
-			'serviceAccess' : service_keys
-		    }
-
-	    if tb == master:
-		# NB, the export_project parameter is a dict that includes
-		# the type
-		req['exportProject'] = export_project
-
-	    # node resources if any
-	    if nodes != None and len(nodes) > 0:
-		rnodes = [ ]
-		for n in nodes:
-		    rn = { }
-		    image, hw, count = n.split(":")
-		    if image: rn['image'] = [ image ]
-		    if hw: rn['hardware'] = [ hw ]
-		    if count: rn['count'] = int(count)
-		    rnodes.append(rn)
-		req['resources']= { }
-		req['resources']['node'] = rnodes
-
-	    try:
-		r = self.call_RequestAccess(uri, req, 
-			self.cert_file, self.cert_pwd, self.trusted_certs)
-	    except service_error, e:
-		if e.code == service_error.access:
-		    self.log.debug("[get_access] Access denied")
-		    r = None
-		    continue
-		else:
-		    raise e
-
-	    if r.has_key('RequestAccessResponseBody'):
-		# Through to here we have a valid response, not a fault.
-		# Access denied is a fault, so something better or worse than
-		# access denied has happened.
-		r = r['RequestAccessResponseBody']
-		self.log.debug("[get_access] Access granted")
-		break
-	    else:
-		raise service_error(service_error.protocol,
-			"Bad proxy response")
-	
-	if not r:
-	    raise service_error(service_error.access, 
-		    "Access denied by %s (%s)" % (tb, uri))
-
-	e = r['emulab']
-	p = e['project']
-	tbparam[tb] = { 
-		"boss": e['boss'],
-		"host": e['ops'],
-		"domain": e['domain'],
-		"fs": e['fileServer'],
-		"eventserver": e['eventServer'],
-		"project": unpack_id(p['name']),
-		"emulab" : e,
-		"allocID" : r['allocID'],
-		}
-	# Make the testbed name be the label the user applied
-	p['testbed'] = {'localname': tb }
-
-	for u in p['user']:
-	    tbparam[tb]['user'] = unpack_id(u['userID'])
-
-	for a in e['fedAttr']:
-	    if a['attribute']:
-		key = translate_attr.get(a['attribute'].lower(), None)
-		if key:
-		    tbparam[tb][key]= a['value']
-	
-    def release_access(self, tb, aid):
-	"""
-	Release access to testbed through fedd
-	"""
-
-	uri = self.tbmap.get(tb, None)
-	if not uri:
-	    raise service_error(serice_error.server_config, 
-		    "Unknown testbed: %s" % tb)
-
-	resp = self.call_ReleaseAccess(uri, {'allocID': aid},
-		self.cert_file, self.cert_pwd, self.trusted_certs)
-
-	# better error coding
-
-    def remote_splitter(self, uri, desc, master):
-
-	req = {
-		'description' : { 'ns2description': desc },
-		'master': master,
-		'include_fedkit': bool(self.fedkit)
-	    }
-
-	r = self.call_Ns2Split(uri, req, self.cert_file, self.cert_pwd, 
-		self.trusted_certs)
-
-	if r.has_key('Ns2SplitResponseBody'):
-	    r = r['Ns2SplitResponseBody']
-	    if r.has_key('output'):
-		return r['output'].splitlines()
-	    else:
-		raise service_error(service_error.protocol, 
-			"Bad splitter response (no output)")
-	else:
-	    raise service_error(service_error.protocol, "Bad splitter response")
-	
-    class current_testbed:
-	"""
-	Object for collecting the current testbed description.  The testbed
-	description is saved to a file with the local testbed variables
-	subsittuted line by line.
-	"""
-	def __init__(self, eid, tmpdir, fedkit):
-	    self.begin_testbed = re.compile("^#\s+Begin\s+Testbed\s+\((\w+)\)")
-	    self.end_testbed = re.compile("^#\s+End\s+Testbed\s+\((\w+)\)")
-	    self.current_testbed = None
-	    self.testbed_file = None
-
-	    self.def_expstart = \
-		    "sudo -H /bin/sh FEDDIR/fed_bootstrap >& /tmp/federate";
-	    self.def_mexpstart = "sudo -H FEDDIR/make_hosts FEDDIR/hosts";
-	    self.def_gwstart = \
-		    "sudo -H FEDDIR/fed-tun.pl -f GWCONF>& /tmp/bridge.log";
-	    self.def_mgwstart = \
-		    "sudo -H FEDDIR/fed-tun.pl -f GWCONF >& /tmp/bridge.log";
-	    self.def_gwimage = "FBSD61-TUNNEL2";
-	    self.def_gwtype = "pc";
-
-	    self.eid = eid
-	    self.tmpdir = tmpdir
-	    self.fedkit = fedkit
-
-	def __call__(self, line, master, allocated, tbparams):
-	    # Capture testbed topology descriptions
-	    if self.current_testbed == None:
-		m = self.begin_testbed.match(line)
-		if m != None:
-		    self.current_testbed = m.group(1)
-		    if self.current_testbed == None:
-			raise service_error(service_error.req,
-				"Bad request format (unnamed testbed)")
-		    allocated[self.current_testbed] = \
-			    allocated.get(self.current_testbed,0) + 1
-		    tb_dir = "%s/%s" % (self.tmpdir, self.current_testbed)
-		    if not os.path.exists(tb_dir):
-			try:
-			    os.mkdir(tb_dir)
-			except IOError:
-			    raise service_error(service_error.internal,
-				    "Cannot create %s" % tb_dir)
-		    try:
-			self.testbed_file = open("%s/%s.%s.tcl" %
-				(tb_dir, self.eid, self.current_testbed), 'w')
-		    except IOError:
-			self.testbed_file = None
-		    return True
-		else: return False
-	    else:
-		m = self.end_testbed.match(line)
-		if m != None:
-		    if m.group(1) != self.current_testbed:
-			raise service_error(service_error.internal, 
-				"Mismatched testbed markers!?")
-		    if self.testbed_file != None: 
-			self.testbed_file.close()
-			self.testbed_file = None
-		    self.current_testbed = None
-		elif self.testbed_file:
-		    # Substitute variables and put the line into the local
-		    # testbed file.
-		    gwtype = tbparams[self.current_testbed].get('gwtype', 
-			    self.def_gwtype)
-		    gwimage = tbparams[self.current_testbed].get('gwimage', 
-			    self.def_gwimage)
-		    mgwstart = tbparams[self.current_testbed].get('mgwstart', 
-			    self.def_mgwstart)
-		    mexpstart = tbparams[self.current_testbed].get('mexpstart', 
-			    self.def_mexpstart)
-		    gwstart = tbparams[self.current_testbed].get('gwstart', 
-			    self.def_gwstart)
-		    expstart = tbparams[self.current_testbed].get('expstart', 
-			    self.def_expstart)
-		    project = tbparams[self.current_testbed].get('project')
-		    line = re.sub("GWTYPE", gwtype, line)
-		    line = re.sub("GWIMAGE", gwimage, line)
-		    if self.current_testbed == master:
-			line = re.sub("GWSTART", mgwstart, line)
-			line = re.sub("EXPSTART", mexpstart, line)
-		    else:
-			line = re.sub("GWSTART", gwstart, line)
-			line = re.sub("EXPSTART", expstart, line)
-		    line = re.sub("GWCONF", "FEDDIR`hostname`.gw.conf", line)
-		    line = re.sub("PROJDIR", "/proj/%s/" % project, line)
-		    line = re.sub("EID", self.eid, line)
-		    line = re.sub("FEDDIR", "/proj/%s/exp/%s/tmp/" % \
-			    (project, self.eid), line)
-		    if self.fedkit:
-			line = re.sub("FEDKIT", os.path.basename(self.fedkit),
-				line)
-		    print >>self.testbed_file, line
-		return True
-
-    class allbeds:
-	"""
-	Process the Allbeds section.  Get access to each federant and save the
-	parameters in tbparams
-	"""
-	def __init__(self, get_access):
-	    self.begin_allbeds = re.compile("^#\s+Begin\s+Allbeds")
-	    self.end_allbeds = re.compile("^#\s+End\s+Allbeds")
-	    self.in_allbeds = False
-	    self.get_access = get_access
-
-	def __call__(self, line, user, tbparams, master, export_project,
-		access_user):
-	    # Testbed access parameters
-	    if not self.in_allbeds:
-		if self.begin_allbeds.match(line):
-		    self.in_allbeds = True
-		    return True
-		else:
-		    return False
-	    else:
-		if self.end_allbeds.match(line):
-		    self.in_allbeds = False
-		else:
-		    nodes = line.split('|')
-		    tb = nodes.pop(0)
-		    self.get_access(tb, nodes, user, tbparams, master,
-			    export_project, access_user)
-		return True
-
-    class gateways:
-	def __init__(self, eid, master, tmpdir, gw_pubkey,
-		gw_secretkey, copy_file, fedkit):
-	    self.begin_gateways = \
-		    re.compile("^#\s+Begin\s+gateways\s+\((\w+)\)")
-	    self.end_gateways = re.compile("^#\s+End\s+gateways\s+\((\w+)\)")
-	    self.current_gateways = None
-	    self.control_gateway = None
-	    self.active_end = { }
-
-	    self.eid = eid
-	    self.master = master
-	    self.tmpdir = tmpdir
-	    self.gw_pubkey_base = gw_pubkey
-	    self.gw_secretkey_base = gw_secretkey
-
-	    self.copy_file = copy_file
-	    self.fedkit = fedkit
-
-
-	def gateway_conf_file(self, gw, master, eid, pubkey, privkey,
-		active_end, tbparams, dtb, myname, desthost, type):
-	    """
-	    Produce a gateway configuration file from a gateways line. 
-	    """
-
-	    sproject = tbparams[gw].get('project', 'project')
-	    dproject = tbparams[dtb].get('project', 'project')
-	    sdomain = ".%s.%s%s" % (eid, sproject,
-		    tbparams[gw].get('domain', ".example.com"))
-	    ddomain = ".%s.%s%s" % (eid, dproject,
-		    tbparams[dtb].get('domain', ".example.com"))
-	    boss = tbparams[master].get('boss', "boss")
-	    fs = tbparams[master].get('fs', "fs")
-	    event_server = "%s%s" % \
-		    (tbparams[gw].get('eventserver', "event_server"),
-			    tbparams[gw].get('domain', "example.com"))
-	    remote_event_server = "%s%s" % \
-		    (tbparams[dtb].get('eventserver', "event_server"),
-			    tbparams[dtb].get('domain', "example.com"))
-	    seer_control = "%s%s" % \
-		    (tbparams[gw].get('control', "control"), sdomain)
-
-	    if self.fedkit:
-		remote_script_dir = "/usr/local/federation/bin"
-		local_script_dir = "/usr/local/federation/bin"
-	    else:
-		remote_script_dir = "/proj/%s/exp/%s/tmp" % ( dproject, eid)
-		local_script_dir = "/proj/%s/exp/%s/tmp" % ( sproject, eid)
-
-	    local_key_dir = "/proj/%s/exp/%s/tmp" % ( sproject, eid)
-	    remote_conf_dir = "/proj/%s/exp/%s/tmp" % ( dproject, eid)
-	    tunnel_cfg = tbparams[gw].get("tun", "false")
-
-	    conf_file = "%s%s.gw.conf" % (myname, sdomain)
-	    remote_conf_file = "%s%s.gw.conf" % (desthost, ddomain)
-
-	    # translate to lower case so the `hostname` hack for specifying
-	    # configuration files works.
-	    conf_file = conf_file.lower();
-	    remote_conf_file = remote_conf_file.lower();
-
-	    if dtb == master:
-		active = "false"
-	    elif gw == master:
-		active = "true"
-	    elif active_end.has_key['%s-%s' % (dtb, gw)]:
-		active = "false"
-	    else:
-		active_end['%s-%s' % (gw, dtb)] = 1
-		active = "true"
-
-	    gwconfig = open("%s/%s/%s" % (self.tmpdir, gw, conf_file), "w")
-	    print >>gwconfig, "Active: %s" % active
-	    print >>gwconfig, "TunnelCfg: %s" % tunnel_cfg
-	    print >>gwconfig, "BossName: %s" % boss
-	    print >>gwconfig, "FsName: %s" % fs
-	    print >>gwconfig, "EventServerName: %s" % event_server
-	    print >>gwconfig, "RemoteEventServerName: %s" % remote_event_server
-	    print >>gwconfig, "SeerControl: %s" % seer_control
-	    print >>gwconfig, "Type: %s" % type
-	    print >>gwconfig, "RemoteScriptDir: %s" % remote_script_dir
-	    print >>gwconfig, "EventRepeater: %s/fed_evrepeater" % \
-		    local_script_dir
-	    print >>gwconfig, "RemoteExperiment: %s/%s" % (dproject, eid)
-	    print >>gwconfig, "LocalExperiment: %s/%s" % (sproject, eid)
-	    print >>gwconfig, "RemoteConfigFile: %s/%s" % \
-		    (remote_conf_dir, remote_conf_file)
-	    print >>gwconfig, "Peer: %s%s" % (desthost, ddomain)
-	    print >>gwconfig, "Pubkeys: %s/%s" % (local_key_dir, pubkey)
-	    print >>gwconfig, "Privkeys: %s/%s" % (local_key_dir, privkey)
-	    gwconfig.close()
-
-	    return active == "true"
-
-	def __call__(self, line, allocated, tbparams):
-	    # Process gateways
-	    if not self.current_gateways:
-		m = self.begin_gateways.match(line)
-		if m:
-		    self.current_gateways = m.group(1)
-		    if allocated.has_key(self.current_gateways):
-			# This test should always succeed
-			tb_dir = "%s/%s" % (self.tmpdir, self.current_gateways)
-			if not os.path.exists(tb_dir):
-			    try:
-				os.mkdir(tb_dir)
-			    except IOError:
-				raise service_error(service_error.internal,
-					"Cannot create %s" % tb_dir)
-		    else:
-			# XXX
-			self.log.error("[gateways]: Ignoring gateways for " + \
-				"unknown testbed %s" % self.current_gateways)
-			self.current_gateways = None
-		    return True
-		else:
-		    return False
-	    else:
-		m = self.end_gateways.match(line)
-		if m :
-		    if m.group(1) != self.current_gateways:
-			raise service_error(service_error.internal,
-				"Mismatched gateway markers!?")
-		    if self.control_gateway:
-			try:
-			    cc = open("%s/%s/client.conf" %
-				    (self.tmpdir, self.current_gateways), 'w')
-			    print >>cc, "ControlGateway: %s" % \
-				    self.control_gateway
-			    if tbparams[self.master].has_key('smbshare'):
-				print >>cc, "SMBSHare: %s" % \
-					tbparams[self.master]['smbshare']
-			    print >>cc, "ProjectUser: %s" % \
-				    tbparams[self.master]['user']
-			    print >>cc, "ProjectName: %s" % \
-				    tbparams[self.master]['project']
-			    cc.close()
-			except IOError:
-			    raise service_error(service_error.internal,
-				    "Error creating client config")
-			try:
-			    cc = open("%s/%s/seer.conf" %
-				    (self.tmpdir, self.current_gateways),
-				    'w')
-			    if self.current_gateways != self.master:
-				print >>cc, "ControlNode: %s" % \
-					self.control_gateway
-			    print >>cc, "ExperimentID: %s/%s" % \
-				    ( tbparams[self.master]['project'], \
-				    self.eid )
-			    cc.close()
-			except IOError:
-			    raise service_error(service_error.internal,
-				    "Error creating seer config")
-		    else:
-			debug.error("[gateways]: No control gateway for %s" %\
-				    self.current_gateways)
-		    self.current_gateways = None
-		else:
-		    dtb, myname, desthost, type = line.split(" ")
-
-		    if type == "control" or type == "both":
-			self.control_gateway = "%s.%s.%s%s" % (myname, 
-				self.eid, 
-				tbparams[self.current_gateways]['project'],
-				tbparams[self.current_gateways]['domain'])
-		    try:
-			active = self.gateway_conf_file(self.current_gateways,
-				self.master, self.eid, self.gw_pubkey_base,
-				self.gw_secretkey_base,
-				self.active_end, tbparams, dtb, myname,
-				desthost, type)
-		    except IOError, e:
-			raise service_error(service_error.internal,
-				"Failed to write config file for %s" % \
-					self.current_gateway)
-	    
-		    gw_pubkey = "%s/keys/%s" % \
-			    (self.tmpdir, self.gw_pubkey_base)
-		    gw_secretkey = "%s/keys/%s" % \
-			    (self.tmpdir, self.gw_secretkey_base)
-
-		    pkfile = "%s/%s/%s" % \
-			    ( self.tmpdir, self.current_gateways, 
-				    self.gw_pubkey_base)
-		    skfile = "%s/%s/%s" % \
-			    ( self.tmpdir, self.current_gateways, 
-				    self.gw_secretkey_base)
-
-		    if not os.path.exists(pkfile):
-			try:
-			    self.copy_file(gw_pubkey, pkfile)
-			except IOError:
-			    service_error(service_error.internal,
-				    "Failed to copy pubkey file")
-
-		    if active and not os.path.exists(skfile):
-			try:
-			    self.copy_file(gw_secretkey, skfile)
-			except IOError:
-			    service_error(service_error.internal,
-				    "Failed to copy secretkey file")
-		return True
-
-    class shunt_to_file:
-	"""
-	Simple class to write data between two regexps to a file.
-	"""
-	def __init__(self, begin, end, filename):
-	    """
-	    Begin shunting on a match of begin, stop on end, send data to
-	    filename.
-	    """
-	    self.begin = re.compile(begin)
-	    self.end = re.compile(end)
-	    self.in_shunt = False
-	    self.file = None
-	    self.filename = filename
-
-	def __call__(self, line):
-	    """
-	    Call this on each line in the input that may be shunted.
-	    """
-	    if not self.in_shunt:
-		if self.begin.match(line):
-		    self.in_shunt = True
-		    try:
-			self.file = open(self.filename, "w")
-		    except:
-			self.file = None
-			raise
-		    return True
-		else:
-		    return False
-	    else:
-		if self.end.match(line):
-		    if self.file: 
-			self.file.close()
-			self.file = None
-		    self.in_shunt = False
-		else:
-		    if self.file:
-			print >>self.file, line
-		return True
-
-    class shunt_to_list:
-	"""
-	Same interface as shunt_to_file.  Data collected in self.list, one list
-	element per line.
-	"""
-	def __init__(self, begin, end):
-	    self.begin = re.compile(begin)
-	    self.end = re.compile(end)
-	    self.in_shunt = False
-	    self.list = [ ]
-	
-	def __call__(self, line):
-	    if not self.in_shunt:
-		if self.begin.match(line):
-		    self.in_shunt = True
-		    return True
-		else:
-		    return False
-	    else:
-		if self.end.match(line):
-		    self.in_shunt = False
-		else:
-		    self.list.append(line)
-		return True
-
-    class shunt_to_string:
-	"""
-	Same interface as shunt_to_file.  Data collected in self.str, all in
-	one string.
-	"""
-	def __init__(self, begin, end):
-	    self.begin = re.compile(begin)
-	    self.end = re.compile(end)
-	    self.in_shunt = False
-	    self.str = ""
-	
-	def __call__(self, line):
-	    if not self.in_shunt:
-		if self.begin.match(line):
-		    self.in_shunt = True
-		    return True
-		else:
-		    return False
-	    else:
-		if self.end.match(line):
-		    self.in_shunt = False
-		else:
-		    self.str += line
-		return True
-
-    def create_experiment(self, req, fid):
-	"""
-	The external interface to experiment creation called from the
-	dispatcher.
-
-	Creates a working directory, splits the incoming description using the
-	splitter script and parses out the avrious subsections using the
-	lcasses above.  Once each sub-experiment is created, use pooled threads
-	to instantiate them and start it all up.
-	"""
-
-	if not self.auth.check_attribute(fid, 'create'):
-	    raise service_error(service_error.access, "Create access denied")
-
-	try:
-	    tmpdir = tempfile.mkdtemp(prefix="split-")
-	except IOError:
-	    raise service_error(service_error.internal, "Cannot create tmp dir")
-
-	gw_pubkey_base = "fed.%s.pub" % self.ssh_type
-	gw_secretkey_base = "fed.%s" % self.ssh_type
-	gw_pubkey = tmpdir + "/keys/" + gw_pubkey_base
-	gw_secretkey = tmpdir + "/keys/" + gw_secretkey_base
-	tclfile = tmpdir + "/experiment.tcl"
-	tbparams = { }
-	try:
-	    access_user = self.accessdb[fid]
-	except KeyError:
-	    raise service_error(service_error.internal,
-		    "Access map and authorizer out of sync in " + \
-			    "create_experiment for fedid %s"  % fid)
-
-	pid = "dummy"
-	gid = "dummy"
-	# XXX
-	fail_soft = False
-
-	try:
-	    os.mkdir(tmpdir+"/keys")
-	except OSError:
-	    raise service_error(service_error.internal,
-		    "Can't make temporary dir")
-
-	req = req.get('CreateRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no CreateRequestBody)")
-	# The tcl parser needs to read a file so put the content into that file
-	descr=req.get('experimentdescription', None)
-	if descr:
-	    file_content=descr.get('ns2description', None)
-	    if file_content:
-		try:
-		    f = open(tclfile, 'w')
-		    f.write(file_content)
-		    f.close()
-		except IOError:
-		    raise service_error(service_error.internal,
-			    "Cannot write temp experiment description")
-	    else:
-		raise service_error(service_error.req, 
-			"Only ns2descriptions supported")
-	else:
-	    raise service_error(service_error.req, "No experiment description")
-
-	if req.has_key('experimentID') and \
-		req['experimentID'].has_key('localname'):
-	    eid = req['experimentID']['localname']
-	    self.state_lock.acquire()
-	    while (self.state.has_key(eid)):
-		eid += random.choice(string.ascii_letters)
-	    # To avoid another thread picking this localname
-	    self.state[eid] = "placeholder"
-	    self.state_lock.release()
-	else:
-	    eid = self.exp_stem
-	    for i in range(0,5):
-		eid += random.choice(string.ascii_letters)
-	    self.state_lock.acquire()
-	    while (self.state.has_key(eid)):
-		eid = self.exp_stem
-		for i in range(0,5):
-		    eid += random.choice(string.ascii_letters)
-	    # To avoid another thread picking this localname
-	    self.state[eid] = "placeholder"
-	    self.state_lock.release()
-
-	try: 
-	    # This catches exceptions to clear the placeholder if necessary
-	    try:
-		self.generate_ssh_keys(gw_secretkey, self.ssh_type)
-	    except ValueError:
-		raise service_error(service_error.server_config, 
-			"Bad key type (%s)" % self.ssh_type)
-
-	    user = req.get('user', None)
-	    if user == None:
-		raise service_error(service_error.req, "No user")
-
-	    master = req.get('master', None)
-	    if not master:
-		raise service_error(service_error.req,
-			"No master testbed label")
-	    export_project = req.get('exportProject', None)
-	    if not export_project:
-		raise service_error(service_error.req, "No export project")
-	    
-	    if self.splitter_url:
-		self.log.debug("Calling remote splitter at %s" % \
-			self.splitter_url)
-		split_data = self.remote_splitter(self.splitter_url,
-			file_content, master)
-	    else:
-		tclcmd = [self.tclsh, self.tcl_splitter, '-s', '-x', 
-		    str(self.muxmax), '-m', master]
-
-		if self.fedkit:
-		    tclcmd.append('-k')
-
-		tclcmd.extend([pid, gid, eid, tclfile])
-
-		self.log.debug("running local splitter %s", " ".join(tclcmd))
-		tclparser = Popen(tclcmd, stdout=PIPE)
-		split_data = tclparser.stdout
-
-	    allocated = { }	    # Testbeds we can access
-	    started = { }	    # Testbeds where a sub-experiment started 
-				# successfully
-
-	    # Objects to parse the splitter output (defined above)
-	    parse_current_testbed = self.current_testbed(eid, tmpdir,
-		    self.fedkit)
-	    parse_allbeds = self.allbeds(self.get_access)
-	    parse_gateways = self.gateways(eid, master, tmpdir,
-		    gw_pubkey_base, gw_secretkey_base, self.copy_file,
-		    self.fedkit)
-	    parse_vtopo = self.shunt_to_string("^#\s+Begin\s+Vtopo",
-			"^#\s+End\s+Vtopo")
-	    parse_hostnames = self.shunt_to_file("^#\s+Begin\s+hostnames",
-			"^#\s+End\s+hostnames", tmpdir + "/hosts")
-	    parse_tarfiles = self.shunt_to_list("^#\s+Begin\s+tarfiles",
-		    "^#\s+End\s+tarfiles")
-	    parse_rpms = self.shunt_to_list("^#\s+Begin\s+rpms",
-		    "^#\s+End\s+rpms")
-
-	    # Working on the split data
-	    for line in split_data:
-		line = line.rstrip()
-		if parse_current_testbed(line, master, allocated, tbparams):
-		    continue
-		elif parse_allbeds(line, user, tbparams, master, export_project,
-			access_user):
-		    continue
-		elif parse_gateways(line, allocated, tbparams):
-		    continue
-		elif parse_vtopo(line):
-		    continue
-		elif parse_hostnames(line):
-		    continue
-		elif parse_tarfiles(line):
-		    continue
-		elif parse_rpms(line):
-		    continue
-		else:
-		    raise service_error(service_error.internal, 
-			    "Bad tcl parse? %s" % line)
-	    # Virtual topology and visualization
-	    vtopo = self.gentopo(parse_vtopo.str)
-	    if not vtopo:
-		raise service_error(service_error.internal, 
-			"Failed to generate virtual topology")
-
-	    vis = self.genviz(vtopo)
-	    if not vis:
-		raise service_error(service_error.internal, 
-			"Failed to generate visualization")
-	    
-	    # save federant information
-	    for k in allocated.keys():
-		tbparams[k]['federant'] = {\
-			'name': [ { 'localname' : eid} ],\
-			'emulab': tbparams[k]['emulab'],\
-			'allocID' : tbparams[k]['allocID'],\
-			'master' : k == master,\
-		    }
-
-
-	    # Copy tarfiles and rpms needed at remote sites into a staging area
-	    try:
-		if self.fedkit:
-		    parse_tarfiles.list.append(self.fedkit)
-		for t in parse_tarfiles.list:
-		    if not os.path.exists("%s/tarfiles" % tmpdir):
-			os.mkdir("%s/tarfiles" % tmpdir)
-		    self.copy_file(t, "%s/tarfiles/%s" % \
-			    (tmpdir, os.path.basename(t)))
-		for r in parse_rpms.list:
-		    if not os.path.exists("%s/rpms" % tmpdir):
-			os.mkdir("%s/rpms" % tmpdir)
-		    self.copy_file(r, "%s/rpms/%s" % \
-			    (tmpdir, os.path.basename(r)))
-	    except IOError, e:
-		raise service_error(service_error.internal, 
-			"Cannot stage tarfile/rpm: %s" % e.strerror)
-
-	except service_error, e:
-	    # If something goes wrong in the parse (usually an access error)
-	    # clear the placeholder state.  From here on out the code delays
-	    # exceptions.
-	    self.state_lock.acquire()
-	    del self.state[eid]
-	    self.state_lock.release()
-	    raise e
-
-	thread_pool_info = self.thread_pool()
-	threads = [ ]
-
-	for tb in [ k for k in allocated.keys() if k != master]:
-	    # Wait until we have a free slot to start the next testbed load
-	    thread_pool_info.acquire()
-	    while thread_pool_info.started - \
-		    thread_pool_info.terminated >= self.nthreads:
-		thread_pool_info.wait()
-	    thread_pool_info.release()
-
-	    # Create and start a thread to start the segment, and save it to
-	    # get the return value later
-	    t  = self.pooled_thread(target=self.start_segment, 
-		    args=(tb, eid, tbparams, tmpdir, 0), name=tb,
-		    pdata=thread_pool_info, trace_file=self.trace_file)
-	    threads.append(t)
-	    t.start()
-
-	# Wait until all finish (the first clause of the while is to make sure
-	# one starts)
-	thread_pool_info.acquire()
-	while thread_pool_info.started == 0 or \
-		thread_pool_info.started > thread_pool_info.terminated:
-	    thread_pool_info.wait()
-	thread_pool_info.release()
-
-	# If none failed, start the master
-	failed = [ t.getName() for t in threads if not t.rv ]
-
-	if len(failed) == 0:
-	    if not self.start_segment(master, eid, tbparams, tmpdir):
-		failed.append(master)
-
-	succeeded = [tb for tb in allocated.keys() if tb not in failed]
-	# If one failed clean up, unless fail_soft is set
-	if failed:
-	    if not fail_soft:
-		for tb in succeeded:
-		    self.stop_segment(tb, eid, tbparams)
-		# Remove the placeholder
-		self.state_lock.acquire()
-		del self.state[eid]
-		self.state_lock.release()
-
-		raise service_error(service_error.federant,
-		    "Swap in failed on %s" % ",".join(failed))
-	else:
-	    self.log.info("[start_segment]: Experiment %s started" % eid)
-
-	# Generate an ID for the experiment (slice) and a certificate that the
-	# allocator can use to prove they own it.  We'll ship it back through
-	# the encrypted connection.
-	(expid, expcert) = generate_fedid("test", dir=tmpdir, log=self.log)
-
-	self.log.debug("[start_experiment]: removing %s" % tmpdir)
-
-	# Walk up tmpdir, deleting as we go
-	for path, dirs, files in os.walk(tmpdir, topdown=False):
-	    for f in files:
-		os.remove(os.path.join(path, f))
-	    for d in dirs:
-		os.rmdir(os.path.join(path, d))
-	os.rmdir(tmpdir)
-
-	# The deepcopy prevents the allocation ID and other binaries from being
-	# translated into other formats
-	resp = { 'federant' : [ copy.deepcopy(tbparams[tb]['federant']) \
-		for tb in tbparams.keys() \
-		    if tbparams[tb].has_key('federant') ],\
-		    'vtopo': vtopo,\
-		    'vis' : vis,
-		    'experimentID' : [\
-			    { 'fedid': copy.copy(expid) }, \
-			    { 'localname': eid },\
-			],\
-		    'experimentAccess': { 'X509' : expcert },\
-		}
-
-	# Insert the experiment into our state and update the disk copy
-	self.state_lock.acquire()
-	self.state[expid] = { 'federant' : [ tbparams[tb]['federant'] \
-		for tb in tbparams.keys() \
-		    if tbparams[tb].has_key('federant') ],\
-		    'vtopo': vtopo,\
-		    'vis' : vis,
-		    'owner': fid,
-		    'experimentID' : [\
-			    { 'fedid': expid }, { 'localname': eid },\
-			],\
-		}
-	self.state[eid] = self.state[expid]
-	if self.state_filename: self.write_state()
-	self.state_lock.release()
-
-	self.auth.set_attribute(fid, expid)
-	self.auth.set_attribute(expid, expid)
-
-	if not failed:
-	    return resp
-	else:
-	    raise service_error(service_error.partial, \
-		    "Partial swap in on %s" % ",".join(succeeded))
-
-    def check_experiment_access(self, fid, key):
-	"""
-	Confirm that the fid has access to the experiment.  Though a request
-	may be made in terms of a local name, the access attribute is always
-	the experiment's fedid.
-	"""
-	if not isinstance(key, fedid):
-	    self.state_lock.acquire()
-	    if self.state.has_key(key):
-		try:
-		    kl = [ f['fedid'] for f in self.state[key]['experimentID']\
-			    if f.has_key('fedid') ]
-		except KeyError:
-		    self.state_lock.release()
-		    raise service_error(service_error.internal, 
-			    "No fedid for experiment %s when checking " +\
-				    "access(!?)" % key)
-		if len(kl) == 1:
-		    key = kl[0]
-		else:
-		    self.state_lock.release()
-		    raise service_error(service_error.internal, 
-			    "multiple fedids for experiment %s when " +\
-				    "checking access(!?)" % key)
-	    else:
-		self.state_lock.release()
-		raise service_error(service_error.access, "Access Denied")
-	    self.state_lock.release()
-
-	if self.auth.check_attribute(fid, key):
-	    return True
-	else:
-	    raise service_error(service_error.access, "Access Denied")
-
-
-
-    def get_vtopo(self, req, fid):
-	"""
-	Return the stored virtual topology for this experiment
-	"""
-	rv = None
-
-	req = req.get('VtopoRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no VtopoRequestBody)")
-	exp = req.get('experiment', None)
-	if exp:
-	    if exp.has_key('fedid'):
-		key = exp['fedid']
-		keytype = "fedid"
-	    elif exp.has_key('localname'):
-		key = exp['localname']
-		keytype = "localname"
-	    else:
-		raise service_error(service_error.req, "Unknown lookup type")
-	else:
-	    raise service_error(service_error.req, "No request?")
-
-	self.check_experiment_access(fid, key)
-
-	self.state_lock.acquire()
-	if self.state.has_key(key):
-	    rv = { 'experiment' : {keytype: key },\
-		    'vtopo': self.state[key]['vtopo'],\
-		}
-	self.state_lock.release()
-
-	if rv: return rv
-	else: raise service_error(service_error.req, "No such experiment")
-
-    def get_vis(self, req, fid):
-	"""
-	Return the stored visualization for this experiment
-	"""
-	rv = None
-
-	req = req.get('VisRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no VisRequestBody)")
-	exp = req.get('experiment', None)
-	if exp:
-	    if exp.has_key('fedid'):
-		key = exp['fedid']
-		keytype = "fedid"
-	    elif exp.has_key('localname'):
-		key = exp['localname']
-		keytype = "localname"
-	    else:
-		raise service_error(service_error.req, "Unknown lookup type")
-	else:
-	    raise service_error(service_error.req, "No request?")
-
-	self.check_experiment_access(fid, key)
-
-	self.state_lock.acquire()
-	if self.state.has_key(key):
-	    rv =  { 'experiment' : {keytype: key },\
-		    'vis': self.state[key]['vis'],\
-		    }
-	self.state_lock.release()
-
-	if rv: return rv
-	else: raise service_error(service_error.req, "No such experiment")
-
-    def get_info(self, req, fid):
-	"""
-	Return all the stored info about this experiment
-	"""
-	rv = None
-
-	req = req.get('InfoRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no VisRequestBody)")
-	exp = req.get('experiment', None)
-	if exp:
-	    if exp.has_key('fedid'):
-		key = exp['fedid']
-		keytype = "fedid"
-	    elif exp.has_key('localname'):
-		key = exp['localname']
-		keytype = "localname"
-	    else:
-		raise service_error(service_error.req, "Unknown lookup type")
-	else:
-	    raise service_error(service_error.req, "No request?")
-
-	self.check_experiment_access(fid, key)
-
-	# The state may be massaged by the service function that called
-	# get_info (e.g., encoded for XMLRPC transport) so send a copy of the
-	# state.
-	self.state_lock.acquire()
-	if self.state.has_key(key):
-	    rv = copy.deepcopy(self.state[key])
-	self.state_lock.release()
-
-	if rv: return rv
-	else: raise service_error(service_error.req, "No such experiment")
-
-
-    def terminate_experiment(self, req, fid):
-	"""
-	Swap this experiment out on the federants and delete the shared
-	information
-	"""
-	tbparams = { }
-	req = req.get('TerminateRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no TerminateRequestBody)")
-	exp = req.get('experiment', None)
-	if exp:
-	    if exp.has_key('fedid'):
-		key = exp['fedid']
-		keytype = "fedid"
-	    elif exp.has_key('localname'):
-		key = exp['localname']
-		keytype = "localname"
-	    else:
-		raise service_error(service_error.req, "Unknown lookup type")
-	else:
-	    raise service_error(service_error.req, "No request?")
-
-	self.check_experiment_access(fid, key)
-
-	self.state_lock.acquire()
-	fed_exp = self.state.get(key, None)
-
-	if fed_exp:
-	    # This branch of the conditional holds the lock to generate a
-	    # consistent temporary tbparams variable to deallocate experiments.
-	    # It releases the lock to do the deallocations and reacquires it to
-	    # remove the experiment state when the termination is complete.
-	    ids = []
-	    #  experimentID is a list of dicts that are self-describing
-	    #  identifiers.  This finds all the fedids and localnames - the
-	    #  keys of self.state - and puts them into ids.
-	    for id in fed_exp.get('experimentID', []):
-		if id.has_key('fedid'): ids.append(id['fedid'])
-		if id.has_key('localname'): ids.append(id['localname'])
-
-	    # Construct enough of the tbparams to make the stop_segment calls
-	    # work
-	    for fed in fed_exp['federant']:
-		try:
-		    for e in fed['name']:
-			eid = e.get('localname', None)
-			if eid: break
-		    else:
-			continue
-
-		    p = fed['emulab']['project']
-
-		    project = p['name']['localname']
-		    tb = p['testbed']['localname']
-		    user = p['user'][0]['userID']['localname']
-
-		    domain = fed['emulab']['domain']
-		    host  = "%s%s" % (fed['emulab']['ops'], domain)
-		    aid = fed['allocID']
-		except KeyError, e:
-		    continue
-		tbparams[tb] = {\
-			'user': user,\
-			'domain': domain,\
-			'project': project,\
-			'host': host,\
-			'eid': eid,\
-			'aid': aid,\
-		    }
-	    self.state_lock.release()
-
-	    # Stop everyone.
-	    for tb in tbparams.keys():
-		self.stop_segment(tb, tbparams[tb]['eid'], tbparams)
-
-	    # release the allocations
-	    for tb in tbparams.keys():
-		self.release_access(tb, tbparams[tb]['aid'])
-
-	    # Remove the terminated experiment
-	    self.state_lock.acquire()
-	    for id in ids:
-		if self.state.has_key(id): del self.state[id]
-
-	    if self.state_filename: self.write_state()
-	    self.state_lock.release()
-
-	    return { 'experiment': exp }
-	else:
-	    # Don't forget to release the lock
-	    self.state_lock.release()
-	    raise service_error(service_error.req, "No saved state")
Index: fedd/fedd/fedid.py
===================================================================
--- fedd/fedd/fedid.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,176 +1,0 @@
-#!/usr/local/bin/python
-
-import os, sys
-import subprocess
-import tempfile
-import logging
-
-from M2Crypto import SSL, X509, EVP
-from pyasn1.codec.der import decoder
-
-# The version of M2Crypto on users is pretty old and doesn't have all the
-# features that are useful.  The legacy code is somewhat more brittle than the
-# main line, but will work.
-if "as_der" not in dir(EVP.PKey):
-    from asn1_raw import get_key_bits_from_file, get_key_bits_from_cert
-    legacy = True
-else:
-    legacy = False
-
-class fedid:
-    """
-    Wrapper around the federated ID from an X509 certificate. 
-    """
-    HASHSIZE=20
-    def __init__(self, bits=None, hexstr=None, cert=None, file=None):
-	if bits != None:
-	    self.set_bits(bits)
-	elif hexstr != None:
-	    self.set_hexstr(hexstr)
-	elif cert != None:
-	    self.set_cert(cert)
-	elif file != None:
-	    self.set_file(file)
-	else:
-	    self.buf = None
-
-    def __hash__(self):
-	return hash(self.buf)
-
-    def __eq__(self, other):
-	if isinstance(other, type(self)):
-	    return self.buf == other.buf
-	elif isinstance(other, type(str())):
-	    return self.buf == other;
-	else:
-	    return False
-
-    def __ne__(self, other): return not self.__eq__(other)
-
-    def __str__(self):
-	if self.buf != None:
-	    return str().join([ "%02x" % ord(x) for x in self.buf])
-	else: return ""
-
-    def __repr__(self):
-	return "fedid(hexstr='%s')" % self.__str__()
-
-    def pack_soap(self):
-	return self.buf
-
-    def pack_xmlrpc(self):
-	return self.buf
-
-    def digest_bits(self, bits):
-	"""Internal function.  Compute the fedid from bits and store in buf"""
-	d = EVP.MessageDigest('sha1')
-	d.update(bits)
-	self.buf = d.final()
-
-
-    def set_hexstr(self, hexstr):
-	h = hexstr.replace(':','')
-	self.buf= str().join([chr(int(h[i:i+2],16)) \
-		for i in range(0,2*fedid.HASHSIZE,2)])
-
-    def get_hexstr(self):
-	"""Return the hexstring representation of the fedid"""
-	return __str__(self)
-
-    def set_bits(self, bits):
-	"""Set the fedid to bits(a 160 bit buffer)"""
-	self.buf = bits
-
-    def get_bits(self):
-	"""Get the 160 bit buffer from the fedid"""
-	return self.buf
-
-    def set_file(self, file):
-	"""Get the fedid from a certificate file
-
-	Calculate the SHA1 hash over the bit string of the public key as
-	defined in RFC3280.
-	"""
-	self.buf = None
-	if legacy: self.digest_bits(get_key_bits_from_file(file))
-	else: self.set_cert(X509.load_cert(file))
-
-    def set_cert(self, cert):
-	"""Get the fedid from a certificate.
-
-	Calculate the SHA1 hash over the bit string of the public key as
-	defined in RFC3280.
-	"""
-
-	self.buf = None
-	if (cert != None):
-	    if legacy:
-		self.digest_bits(get_key_bits_from_cert(cert))
-	    else:
-		b = []
-		k = cert.get_pubkey()
-
-		# Getting the key was easy, but getting the bit string of the
-		# key requires a side trip through ASN.1
-		dec = decoder.decode(k.as_der())
-
-		# kv is a tuple of the bits in the key.  The loop below
-		# recombines these into bytes and then into a buffer for the
-		# SSL digest function.
-		kv =  dec[0].getComponentByPosition(1)
-		for i in range(0, len(kv), 8):
-		    v = 0
-		    for j in range(0, 8):
-			v = (v << 1) + kv[i+j]
-		    b.append(v)
-		# The comprehension turns b from a list of bytes into a buffer
-		# (string) of bytes
-		self.digest_bits(str().join([chr(x) for x in b]))
-
-def generate_fedid(subj, bits=2048, log=None, dir=None, trace=sys.stderr,
-	ssl_prog="/usr/bin/openssl"):
-    """
-    Create a new certificate and derive a fedid from it.
-
-    The fedid and the certificate are returned as a tuple.
-    """
-
-    keypath = None
-    certpath = None
-    try:
-	try:
-	    kd, keypath = tempfile.mkstemp(dir=dir, prefix="key",
-		    suffix=".pem")
-	    cd, certpath = tempfile.mkstemp(dir=dir, prefix="cert",
-		    suffix=".pem")
-
-	    cmd = [ssl_prog, "req", "-text", "-newkey", 
-		    "rsa:%d" % bits, "-keyout", keypath,  "-nodes", 
-		    "-subj", "/CN=%s" % subj, "-x509", "-days", "30", 
-		    "-out", certpath]
-
-	    if log:
-		log.debug("[generate_fedid] %s" % " ".join(cmd))
-
-	    if trace: call_out = trace
-	    else: 
-		call_out = open("/dev/null", "w")
-
-	    rv = subprocess.call(cmd, stdout=call_out, stderr=call_out)
-	    log.debug("rv = %d" % rv)
-	    if rv == 0:
-		cert = ""
-		for p in (certpath, keypath):
-		    f = open(p)
-		    for line in f:
-			cert += line
-		
-		fid = fedid(file=certpath)
-		return (fid, cert)
-	    else:
-		return (None, None)
-	except IOError, e:
-	    raise e
-    finally:
-	if keypath: os.remove(keypath)
-	if certpath: os.remove(certpath)
Index: fedd/fedd/fixed_resource.py
===================================================================
--- fedd/fedd/fixed_resource.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,49 +1,0 @@
-#!/usr/local/bin/python
-
-# Encode the set of fixed keys as a colon-separated user/key pairs.  This also
-# includes a command line utility to manipulate the DB.
-
-def read_key_db(file):
-    """
-    Read the set of fixed keys fom the file
-    """
-    keys = set()
-
-    f = open(file, "r")
-    for line in f:
-	u, k = line.rstrip().split(':', 2)
-	keys.add((u, k))
-    f.close()
-    return keys
-
-def write_key_db(file, keys):
-    """
-    Write the set of keys to the given file
-    """
-
-    f = open(file, 'w')
-    for t in keys:
-	print >>f, "%s:%s" % t
-    f.close()
-
-def read_project_db(file):
-    """
-    Read the set of fixed keys fom the file
-    """
-    projects = set()
-
-    f = open(file, "r")
-    for line in f:
-	projects.add(line.rstrip())
-    f.close()
-    return projects
-
-def write_project_db(file, projects):
-    """
-    Write the set of keys to the given file
-    """
-
-    f = open(file, 'w')
-    for p in projects:
-	print >>f, "%s" % p
-    f.close()
Index: fedd/fedd/parse_detail.py
===================================================================
--- fedd/fedd/parse_detail.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,39 +1,0 @@
-#!/usr/local/bin/python
-
-from fedd_services import *
-
-from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
-from ZSI.wstools.Namespaces import SOAP
-from ZSI.fault import FaultType
-
-
-# This is a bit of minimal trickery to integrate detail parsing of SOAP faults.
-# ZSI expects detail fields to parse to a class with an "any" attribute.  This
-# little class has such an attribute pointed at itself, so when
-# ZSI.fault.FaultFromMessage extracts that field, it places this object into
-# the new Fault object.  One can also get around this using an extra XML field
-# in the message, parsing the wrapper to the any attribute and then actial body
-# to the class in the any attribute, but this does not perturb the WSDL.
-class detail_faultType:
-    def __init__(self):
-	self.FeddFaultBody = None
-	self.any = self
-    def get_element_FeddFaultBody(self):
-	return self.FeddFaultBody
-
-# Override ZSI.fault.FaultType.typecode - give a parser that speaks our
-# language.
-FaultType.typecode = \
-    Struct(FaultType,
-    [QName(pname='faultcode'),
-    String(pname='faultstring'),
-    URI(pname=(SOAP.ENV,'faultactor'), minOccurs=0),
-    Struct(pname='detail', aname='detail', 
-	ofwhat=[ns0.faultType_Def((ns0.faultType_Def.schema, "FeddFaultBody"))],
-	pyclass=detail_faultType, minOccurs=0),
-    ],  
-    pname=(SOAP.ENV,'Fault'),
-    inline=True,
-    hasextras=0,
-)
-
Index: fedd/fedd/remote_service.py
===================================================================
--- fedd/fedd/remote_service.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,460 +1,0 @@
-#!/usr/local/bin/python
-
-import copy
-
-from socket import error as socket_error
-
-import M2Crypto.httpslib
-from M2Crypto import SSL
-from M2Crypto.m2xmlrpclib import SSL_Transport
-from M2Crypto.SSL import SSLError
-from ZSI import ParseException, FaultException, SoapWriter
-
-from service_error import service_error
-from xmlrpclib import ServerProxy, dumps, loads, Fault, Error, Binary
-
-import fedd_services
-import fedd_internal_services
-from util import fedd_ssl_context
-from fedid import fedid
-import parse_detail
-
-# Turn off the matching of hostname to certificate ID
-SSL.Connection.clientPostConnectionCheck = None
-
-# Used by the remote_service_base class.
-def to_binary(o):
-    """
-    A function that converts an object into an xmlrpclib.Binary using
-    either its internal packing method, or the standard Binary constructor.
-    """
-    pack = getattr(o, 'pack_xmlrpc', None)
-    if callable(pack): return Binary(pack())
-    else: return Binary(o)
-
-# Classes that encapsulate the process of making and dealing with requests to
-# WSDL-generated and XMLRPC remote accesses.  
-
-class remote_service_base:
-    """
-    This invisible base class encapsulates the functions used to massage the
-    dictionaries used to pass parameters into and out of the RPC formats.  It's
-    mostly a container for the static methods to do that work, but defines some
-    maps sued by sub classes on apply_to_tags
-    """
-    # A map used to convert fedid fields to fedid objects (when the field is
-    # already a string)
-    fedid_to_object = ( ('fedid', lambda x: fedid(bits=x)),)
-    # A map used by apply_to_tags to convert fedids from xmlrpclib.Binary
-    # objects to fedid objects in one sweep.
-    decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
-    # A map used to encapsulate fedids into xmlrpclib.Binary objects
-    encap_fedids = (('fedid', to_binary),)
-
-    @staticmethod
-    def pack_soap(container, name, contents):
-	"""
-	Convert the dictionary in contents into a tree of ZSI classes.
-
-	The holder classes are constructed from factories in container and
-	assigned to either the element or attribute name.  This is used to
-	recursively create the SOAP message.
-	"""
-	if getattr(contents, "__iter__", None) != None:
-	    attr =getattr(container, "new_%s" % name, None)
-	    if attr: obj = attr()
-	    else:
-		raise TypeError("%s does not have a new_%s attribute" % \
-			(container, name))
-	    for e, v in contents.iteritems():
-		assign = getattr(obj, "set_element_%s" % e, None) or \
-			getattr(obj, "set_attribute_%s" % e, None)
-		if isinstance(v, type(dict())):
-		    assign(remote_service_base.pack_soap(obj, e, v))
-		elif getattr(v, "__iter__", None) != None:
-		    assign([ remote_service_base.pack_soap(obj, e, val ) \
-			    for val in v])
-		elif getattr(v, "pack_soap", None) != None:
-		    assign(v.pack_soap())
-		else:
-		    assign(v)
-	    return obj
-	else: return contents
-
-    @staticmethod
-    def unpack_soap(element):
-	"""
-	Convert a tree of ZSI SOAP classes intro a hash.  The inverse of
-	pack_soap
-
-	Elements or elements that are empty are ignored.
-	"""
-	methods = [ m for m in dir(element) \
-		if m.startswith("get_element") or m.startswith("get_attribute")]
-	if len(methods) > 0:
-	    rv = { }
-	    for m in methods:
-		if m.startswith("get_element_"):
-		    n = m.replace("get_element_","",1)
-		else:
-		    n = m.replace("get_attribute_", "", 1)
-		sub = getattr(element, m)()
-		if sub != None:
-		    if isinstance(sub, basestring):
-			rv[n] = sub
-		    elif getattr(sub, "__iter__", None) != None:
-			if len(sub) > 0: rv[n] = \
-				[remote_service_base.unpack_soap(e) \
-				    for e in sub]
-		    else:
-			rv[n] = remote_service_base.unpack_soap(sub)
-	    return rv
-	else: 
-	    return element
-
-    @staticmethod
-    def apply_to_tags(e, map):
-	"""
-	Map is an iterable of ordered pairs (tuples) that map a key to a
-	function.
-	This function walks the given message and replaces any object with a
-	key in the map with the result of applying that function to the object.
-	"""
-	if isinstance(e, dict):
-	    for k in e.keys():
-		for tag, fcn in map:
-		    if k == tag:
-			if isinstance(e[k], list):
-			    e[k] = [ fcn(b) for b in e[k]]
-			else:
-			    e[k] = fcn(e[k])
-		    elif isinstance(e[k], dict):
-			remote_service_base.apply_to_tags(e[k], map)
-		    elif isinstance(e[k], list):
-			for ee in e[k]:
-			    remote_service_base.apply_to_tags(ee, map)
-	# Other types end the recursion - they should be leaves
-	return e
-
-    @staticmethod
-    def strip_unicode(obj):
-	"""Walk through a message and convert all strings to non-unicode
-	strings"""
-	if isinstance(obj, dict):
-	    for k in obj.keys():
-		obj[k] = remote_service_base.strip_unicode(obj[k])
-	    return obj
-	elif isinstance(obj, basestring) and not isinstance(obj, str):
-	    return str(obj)
-	elif getattr(obj, "__iter__", None):
-	    return [ remote_service_base.strip_unicode(x) for x in obj]
-	else:
-	    return obj
-
-    @staticmethod
-    def make_unicode(obj):
-	"""Walk through a message and convert all strings to unicode"""
-	if isinstance(obj, dict):
-	    for k in obj.keys():
-		obj[k] = remote_service_base.make_unicode(obj[k])
-	    return obj
-	elif isinstance(obj, basestring) and not isinstance(obj, unicode):
-	    return unicode(obj)
-	elif getattr(obj, "__iter__", None):
-	    return [ remote_service_base.make_unicode(x) for x in obj]
-	else:
-	    return obj
-
-
-
-class soap_handler(remote_service_base):
-    """
-    Encapsulate the handler code to unpack and pack SOAP requests and responses
-    and call the given method.
-
-    The code to decapsulate and encapsulate parameters encoded in SOAP is the
-    same modulo a few parameters.  This is a functor that calls a fedd service
-    trhough a soap interface.  The parameters are the typecode of the request
-    parameters, the method to call (usually a bound instance of a method on a
-    fedd service providing class), the constructor of a response packet and the
-    name of the body element of that packet.  The handler takes a ParsedSoap
-    object (the request) and returns an instance of the class created by
-    constructor containing the response.  Failures of the constructor or badly
-    created constructors will result in None being returned.
-    """
-    def __init__(self, service_name, method, typecode=None,
-	    constructor=None, body_name=None):
-	self.method = method
-
-	response_class_name = "%sResponseMessage" % service_name
-	request_class_name = "%sRequestMessage" % service_name
-
-	if body_name: self.body_name = body_name
-	else: self.body_name = "%sResponseBody" % service_name
-
-	if constructor: self.constructor = constructor
-	else:
-	    self.constructor = self.get_class(response_class_name)
-	    if not self.constructor:
-		raise service_error(service_error.internal,
-			"Cannot find class for %s" % response_class_name)
-
-	if typecode: self.typecode = typecode
-	else: 
-	    req = self.get_class(request_class_name)
-	    if req:
-		self.typecode = req.typecode
-	    else:
-		raise service_error(service_error.internal,
-			"Cannot find class for %s" % request_class_name)
-
-	    if not self.typecode:
-		raise service_error(service_error.internal,
-			"Cannot get typecode for %s" % class_name)
-
-    def get_class(self, class_name):
-	return getattr(fedd_services, class_name, None) or \
-		getattr(fedd_internal_services, class_name, None)
-
-    def __call__(self, ps, fid):
-	req = ps.Parse(self.typecode)
-	# Convert the message to a dict with the fedid strings converted to
-	# fedid objects
-	req = self.apply_to_tags(self.unpack_soap(req), self.fedid_to_object)
-
-	msg = self.method(req, fid)
-
-	resp = self.constructor()
-	set_element = getattr(resp, "set_element_%s" % self.body_name, None)
-	if set_element and callable(set_element):
-	    try:
-		set_element(self.pack_soap(resp, self.body_name, msg))
-		return resp
-	    except (NameError, TypeError):
-		return None
-	else:
-	    return None
-
-class xmlrpc_handler(remote_service_base):
-    """
-    Generate the handler code to unpack and pack XMLRPC requests and responses
-    and call the given method.
-
-    The code to marshall and unmarshall XMLRPC parameters to and from a fedd
-    service is largely the same.  This helper creates such a handler.  The
-    parameters are the method name, and the name of the body struct that
-    contains the response.  A handler is created that takes the params response
-    from an xmlrpclib.loads on the incoming rpc and a fedid and responds with
-    a hash representing the struct ro be returned to the other side.  On error
-    None is returned.  Fedid fields are decapsulated from binary and converted
-    to fedid objects on input and encapsulated as Binaries on output.
-    """
-    def __init__(self, service_name, method):
-	self.method = method
-	self.body_name = "%sResponseBody" % service_name
-
-    def __call__(self, params, fid):
-	msg = None
-
-	p = self.apply_to_tags(params[0], self.decap_fedids)
-	try:
-	    msg = self.method(p, fid)
-	except service_error, e:
-	    raise Fault(e.code_string(), e.desc)
-	if msg != None:
-	    return self.make_unicode(self.apply_to_tags(\
-		    { self.body_name: msg }, self.encap_fedids))
-	else:
-	    return None
-
-class service_caller(remote_service_base):
-    def __init__(self, service_name, request_message=None, 
-	    request_body_name=None, tracefile=None):
-	self.service_name = service_name
-
-	if getattr(fedd_services.feddBindingSOAP, service_name, None):
-	    self.locator = fedd_services.feddServiceLocator
-	    self.port_name = 'getfeddPortType'
-	elif getattr(fedd_internal_services.feddInternalBindingSOAP, 
-		service_name, None):
-	    self.locator = fedd_internal_services.feddInternalServiceLocator
-	    self.port_name = 'getfeddInternalPortType'
-
-	if request_message: self.request_message = request_message
-	else:
-	    request_message_name = "%sRequestMessage" % service_name
-	    self.request_message = \
-		    getattr(fedd_services, request_message_name, None) or \
-		    getattr(fedd_internal_services, request_message_name, None)
-	    if not self.request_message:
-		raise service_error(service_error.internal,
-			"Cannot find class for %s" % request_message_name)
-
-	if request_body_name: self.request_body_name = request_body_name
-	else: self.request_body_name = "%sRequestBody" % service_name
-
-	self.tracefile = tracefile
-	self.__call__ = self.call_service
-
-    def serialize_soap(self, req):
-	"""
-	Return a string containing the message that would be sent to call this
-	service with the given request.
-	"""
-	msg = self.request_message()
-	set_element = getattr(msg, "set_element_%s" % self.request_body_name,
-		None)
-	if not set_element:
-	    raise service_error(service_error.internal,
-		    "Cannot get element setting method for %s" % \
-			    self.request_body_name)
-	set_element(self.pack_soap(msg, self.request_body_name, req))
-	sw = SoapWriter()
-	sw.serialize(msg)
-	return unicode(sw)
-
-    def call_xmlrpc_service(self, url, req, cert_file=None, cert_pwd=None, 
-	    trusted_certs=None, context=None, tracefile=None):
-	"""Send an XMLRPC request.  """
-
-
-	# If a context is given, use it.  Otherwise construct one from
-	# components.  The construction shouldn't call out for passwords.
-	if context:
-	    ctx = context
-	else:
-	    try:
-		ctx = fedd_ssl_context(cert_file, trusted_certs, 
-			password=cert_pwd)
-	    except SSL.SSLError:
-		raise service_error(service_error.server_config,
-			"Certificates misconfigured")
-
-	# Of all the dumbass things.  The XMLRPC library in use here won't
-	# properly encode unicode strings, so we make a copy of req with
-	# the unicode objects converted.  We also convert the url to a
-	# basic string if it isn't one already.
-	r = self.strip_unicode(copy.deepcopy(req))
-	url = str(url)
-	
-	transport = SSL_Transport(ctx)
-	port = ServerProxy(url, transport=transport)
-	# Make the call, and convert faults back to service_errors
-	try:
-	    remote_method = getattr(port, self.service_name, None)
-	    resp = remote_method(self.apply_to_tags(\
-		    { self.request_body_name: r}, self.encap_fedids))
-	except socket_error, e:
-	    raise service_error(service_error.protocol, 
-		    "Cannot connect to %s: %s" % (url, e[1]))
-	except SSLError, e:
-	    raise service_error(service_error.protocol,
-		    "SSL error contacting %s: %s" % (url, e.message))
-	except Fault, f:
-	    raise service_error(None, f.faultString, f.faultCode)
-	except Error, e:
-	    raise service_error(service_error.protocol, 
-		    "Remote XMLRPC Fault: %s" % e)
-
-	return self.apply_to_tags(resp, self.decap_fedids) 
-
-    def call_soap_service(self, url, req, cert_file=None, cert_pwd=None,
-	    trusted_certs=None, context=None, tracefile=None):
-	"""
-	Send req on to the real destination in dt and return the response
-
-	Req is just the requestType object.  This function re-wraps it.  It
-	also rethrows any faults.
-	"""
-
-	tf = tracefile or self.tracefile or None
-
-	# If a context is given, use it.  Otherwise construct one from
-	# components.  The construction shouldn't call out for passwords.
-	if context:
-	    ctx = context
-	else:
-	    try:
-		ctx = fedd_ssl_context(cert_file, trusted_certs, 
-			password=cert_pwd)
-	    except SSL.SSLError:
-		raise service_error(service_error.server_config,
-			"Certificates misconfigured")
-	loc = self.locator()
-	get_port = getattr(loc, self.port_name, None)
-	if not get_port:
-	    raise service_error(service_error.internal, 
-		    "Cannot get port %s from locator" % self.port_name)
-	port = get_port(url,
-		transport=M2Crypto.httpslib.HTTPSConnection, 
-		transdict={ 'ssl_context' : ctx },
-		tracefile=tf)
-	remote_method = getattr(port, self.service_name, None)
-	if not remote_method:
-	    raise service_error(service_error.internal,
-		    "Cannot get service from SOAP port")
-
-	# Reconstruct the full request message
-	msg = self.request_message()
-	set_element = getattr(msg, "set_element_%s" % self.request_body_name,
-		None)
-	if not set_element:
-	    raise service_error(service_error.internal,
-		    "Cannot get element setting method for %s" % \
-			    self.request_body_name)
-	set_element(self.pack_soap(msg, self.request_body_name, req))
-	try:
-	    resp = remote_method(msg)
-	except socket_error, e:
-	    raise service_error(service_error.protocol, 
-		    "Cannot connect to %s: %s" % (url, e[1]))
-	except SSLError, e:
-	    raise service_error(service_error.protocol,
-		    "SSL error contacting %s: %s" % (url, e.message))
-	except ParseException, e:
-	    raise service_error(service_error.protocol,
-		    "Bad format message (XMLRPC??): %s" % e)
-	except FaultException, e:
-	    ee = self.unpack_soap(e.fault.detail[0]).get('FeddFaultBody', { })
-	    if ee:
-		raise service_error(ee['code'], ee['desc'])
-	    else:
-		raise service_error(service_error.internal,
-			"Unexpected fault body")
-	# Unpack and convert fedids to objects
-	r = self.apply_to_tags(self.unpack_soap(resp), self.fedid_to_object)
-	#  Make sure all strings are unicode
-	r = self.make_unicode(r)
-	return r
-
-    def call_service(self, url, req, cert_file=None, cert_pwd=None, 
-	trusted_certs=None, context=None, tracefile=None):
-	p_fault = None	# Any SOAP failure (sent unless XMLRPC works)
-	resp = None
-	try:
-	    # Try the SOAP request
-	    resp = self.call_soap_service(url, req, 
-		    cert_file, cert_pwd, trusted_certs, context, tracefile)
-	    return resp
-	except service_error, e:
-	    if e.code == service_error.protocol: p_fault = None
-	    else: raise
-	except FaultException, f:
-	    p_fault = f.fault.detail[0]
-		
-
-	# If we could not get a valid SOAP response to the request above,
-	# try the same address using XMLRPC and let any faults flow back
-	# out.
-	if p_fault == None:
-	    resp = self.call_xmlrpc_service(url, req, cert_file,
-		    cert_pwd, trusted_certs, context, tracefile)
-	    return resp
-	else:
-	    # Build the fault
-	    ee = unpack_soap(p_fault).get('FeddFaultBody', { })
-	    if ee:
-		raise service_error(ee['code'], ee['desc'])
-	    else:
-		raise service_error(service_error.internal,
-			"Unexpected fault body")
Index: fedd/fedd/server.py
===================================================================
--- fedd/fedd/server.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,223 +1,0 @@
-#!/usr/local/bin/python
-
-import sys
-
-from BaseHTTPServer import BaseHTTPRequestHandler
-
-from ZSI import Fault, ParseException, FaultFromNotUnderstood, \
-    FaultFromZSIException, FaultFromException, ParsedSoap, SoapWriter
-
-from M2Crypto import SSL
-from M2Crypto.SSL.SSLServer import ThreadingSSLServer
-import xmlrpclib
-
-from fedid import fedid
-from fedd_services import ns0
-from service_error import *
-
-import logging
-
-# The SSL server here is based on the implementation described at
-# http://www.xml.com/pub/a/ws/2004/01/20/salz.html
-
-# Turn off the matching of hostname to certificate ID
-SSL.Connection.clientPostConnectionCheck = None
-
-class server(ThreadingSSLServer):
-    """
-    Interface the fedd services to the XMLRPC and SOAP interfaces
-    """
-    def __init__(self, ME, handler, ssl_ctx, impl):
-	"""
-	Create an SSL server that handles the transport in handler using the
-	credentials in ssl_ctx, and interfacing to the implementation of fedd
-	services in fedd.  ME is the host port pair on which to bind.
-	"""
-	ThreadingSSLServer.__init__(self, ME, handler, ssl_ctx)
-	self.impl = impl
-	self.soap_methods = impl.soap_services
-	self.xmlrpc_methods = impl.xmlrpc_services
-	self.log = logging.getLogger("fedd")
-
-    def ahandle_error(self, request, address):
-	"""
-	The default SSLServer prints a stack trace here.  This is a little
-	friendlier.
-	"""
-	if request or address:
-	    self.log.warn("[fedd] Error on incoming connection: %s %s" % \
-		    (request, address))
-	else:
-	    self.log.warn("[fedd] Error on incoming connection " + \
-		    "(Likely SSL error)")
-
-class soap_handler(BaseHTTPRequestHandler):
-    """
-    Standard connection between SOAP and the fedd services in impl.
-
-    Much of this is boilerplate from 
-    http://www.xml.com/pub/a/ws/2004/01/20/salz.html
-    """
-    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
-
-    def send_xml(self, text, code=200):
-	"""Send an XML document as reply"""
-	self.send_response(code)
-	self.send_header('Content-type', 'text/xml; charset="utf-8"')
-	self.send_header('Content-Length', str(len(text)))
-	self.end_headers()
-	self.wfile.write(text)
-	self.wfile.flush()
-	self.request.socket.close()
-
-    def send_fault(self, f, code=500):
-	"""Send a SOAP encoded fault as reply"""
-	self.send_xml(f.AsSOAP(processContents="lax"), code)
-
-    def check_headers(self, ps):
-	"""Send a fault for any required envelope headers"""
-	for (uri, localname) in ps.WhatMustIUnderstand():
-	    self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd'))
-	    return False
-	return  True
-
-    def check_method(self, ps):
-	"""Confirm that this class implements the namespace and SOAP method"""
-	root = ps.body_root
-	if root.namespaceURI not in self.server.impl.soap_namespaces:
-	    self.send_fault(Fault(Fault.Client, 
-		'Unknown namespace "%s"' % root.namespaceURI))
-	    return False
-
-	if getattr(root, 'localName', None) == None:
-	    self.send_fault(Fault(Fault.Client, 'No method"'))
-	    return False
-	return True
-
-    def do_POST(self):
-	"""Treat an HTTP POST request as a SOAP service call"""
-	try:
-	    cl = int(self.headers['content-length'])
-	    data = self.rfile.read(cl)
-	    ps = ParsedSoap(data)
-	except ParseException, e:
-	    self.send_fault(Fault(Fault.Client, str(e)))
-	    return
-	except Exception, e:
-	    self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
-	    return
-	if not self.check_headers(ps): return
-	if not self.check_method(ps): return
-	try:
-	    resp = self.soap_dispatch(ps.body_root.localName, ps,
-		    fedid(cert=self.request.get_peer_cert()))
-	except Fault, f:
-	    self.send_fault(f)
-	    resp = None
-	
-	if resp != None:
-	    sw = SoapWriter()
-	    sw.serialize(resp)
-	    self.send_xml(str(sw))
-
-    def log_request(self, code=0, size=0):
-	"""
-	Log request to the fedd logger
-	"""
-	self.server.log.info("Successful SOAP request code %d" % code)
-
-    def soap_dispatch(self, method, req, fid):
-	"""
-	The connection to the implementation, using the  method maps
-
-	The implementation provides a mapping from SOAP method name to the
-	method in the implementation that provides the service.
-	"""
-	if self.server.soap_methods.has_key(method):
-	    try:
-		return self.server.soap_methods[method](req, fid)
-	    except service_error, e:
-		de = ns0.faultType_Def(
-			(ns0.faultType_Def.schema,
-			    "FeddFaultBody")).pyclass()
-		de._code=e.code
-		de._errstr=e.code_string()
-		de._desc=e.desc
-		if  e.is_server_error():
-		    raise Fault(Fault.Server, e.code_string(), detail=de)
-		else:
-		    raise Fault(Fault.Client, e.code_string(), detail=de)
-	else:
-	    raise Fault(Fault.Client, "Unknown method: %s" % method)
-
-
-class xmlrpc_handler(BaseHTTPRequestHandler):
-    """
-    Standard connection between XMLRPC and the fedd services in impl.
-
-    Much of this is boilerplate from 
-    http://www.xml.com/pub/a/ws/2004/01/20/salz.html
-    """
-    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
-
-    def send_xml(self, text, code=200):
-	"""Send an XML document as reply"""
-	self.send_response(code)
-	self.send_header('Content-type', 'text/xml; charset="utf-8"')
-	self.send_header('Content-Length', str(len(text)))
-	self.end_headers()
-	self.wfile.write(text)
-	self.wfile.flush()
-	# Make sure to close the socket when we're done
-	self.request.socket.close()
-
-    def do_POST(self):
-	"""Treat an HTTP POST request as an XMLRPC service call"""
-	# NB: XMLRPC faults are not HTTP errors, so the code is always 200,
-	# unless an HTTP error occurs, which we don't handle.
-
-	resp = None
-	data = None
-	method = None
-	cl = int(self.headers['content-length'])
-	data = self.rfile.read(cl)
-
-	try:
-	    params, method = xmlrpclib.loads(data)
-	except xmlrpclib.ResponseError:
-	    data = xmlrpclib.dumps(xmlrpclib.Fault("Client", 
-		"Malformed request"), methodresponse=True)
-
-	if method != None:
-	    try:
-		resp = self.xmlrpc_dispatch(method, params,
-			    fedid(cert=self.request.get_peer_cert()))
-		data = xmlrpclib.dumps((resp,), encoding='UTF-8', 
-			methodresponse=True)
-	    except xmlrpclib.Fault, f:
-		data = xmlrpclib.dumps(f, methodresponse=True)
-		resp = None
-
-	self.send_xml(data)
-
-    def log_request(self, code=0, size=0):
-	"""
-	Log request to the fedd logger
-	"""
-	self.server.log.info("Successful XMLRPC request code %d" % code)
-
-
-    def xmlrpc_dispatch(self, method, req, fid):
-	"""
-	The connection to the implementation, using the  method maps
-
-	The implementation provides a mapping from XMLRPC method name to the
-	method in the implementation that provides the service.
-	"""
-	if self.server.xmlrpc_methods.has_key(method):
-	    try:
-		return self.server.xmlrpc_methods[method](req, fid)
-	    except service_error, e:
-		raise xmlrpclib.Fault(e.code_string(), e.desc)
-	else:
-	    raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
Index: fedd/fedd/service_error.py
===================================================================
--- fedd/fedd/service_error.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,46 +1,0 @@
-#!/usr/local/bin/python
-
-# This is used to make the service error reporting independent of the
-# transport.  The XMLRPC and SOAP dispatchers will convert it into
-# transport-specific errors
-class service_error(RuntimeError):
-    access = 1
-    protocol= 2
-    req = 3
-    server_config = 4
-    internal = 5
-    partial = 6
-    federant = 7
-    code_str = { 
-	access : "Access Denied",
-	protocol : "Protocol Error",
-	req : "Badly Formed Request",
-	server_config: "Server Configuration Error",
-	internal : "Internal Error",
-	partial: "Partial embedding",
-	federant: "Federant error"
-    }
-    str_code = dict([ (v, k) for k, v in code_str.iteritems() ])
-    client_errors = ( req )
-    server_errors = ( access, protocol, server_config, internal)
-
-    def __init__(self, code=None, desc=None, from_string=None):
-	self.code = code
-	self.desc = desc
-	if code == None:
-	    self.set_code_from_string(from_string)
-	RuntimeError.__init__(self, desc)
-
-    def code_string(self, code=None):
-	code = code or self.code
-	return service_error.code_str.get(code)
-    
-    def set_code_from_string(self, errstr):
-	self.code = service_error.str_code.get(errstr, service_error.internal)
-	return self.code
-    
-    def is_client_error(self):
-	return self.code in service_error.client_errors
-
-    def is_server_error(self):
-	return self.code in service_error.server_errors
Index: fedd/fedd/split.py
===================================================================
--- fedd/fedd/split.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,154 +1,0 @@
-#!/usr/local/bin/python
-
-import os,sys
-import subprocess
-import tempfile
-import logging
-
-from util import *
-from fedid import fedid
-from remote_service import xmlrpc_handler, soap_handler
-from service_error import *
-from authorizer import authorizer
-
-
-class nullHandler(logging.Handler):
-    def emit(self, record): pass
-
-fl = logging.getLogger("fedd.splitter")
-fl.addHandler(nullHandler())
-
-class split_local:
-    def __init__(self, config=None, auth=None):
-	"""
-	Intialize the various attributes, most from the config object
-	"""
-	self.debug = config.getboolean("splitter", "debug")
-	self.muxmax = config.getint("splitter", "muxmax", 3)
-	self.tclsh = config.get("splitter", "tclsh", 
-		"/usr/local/bin/otclsh")
-	self.tcl_splitter = config.get("splitter", "tcl_splitter",
-		"/usr/testbed/lib/ns2ir/parse.tcl")
-	access_db = config.get("splitter", "access_db", None)
-	allow_any = config.getboolean("splitter", "allow_any", False)
-
-	self.log = logging.getLogger("fedd.splitter")
-	set_log_level(config, "splitter", self.log)
-	self.trace_file = sys.stderr
-
-	set_log_level(config, "splitter", self.log)
-
-	if auth:
-	    self.auth= auth
-	else:
-	    self.auth = authorizer()
-	    self.log.warning("[splitter] No authorizer passed in, " +\
-		    "using local one")
-
-	if access_db and allow_any: 
-	    raise service_error(service_error.internal, 
-		    "Cannot specify both an access database and allow_any " +\
-			    "for splitter")
-	
-	if access_db:
-	    try:
-		read_simple_accessdb(access_db, self.auth, 'split')
-	    except IOError, e:
-		raise service_error(service_error.internal,
-			"Error reading accessDB %s: %s" % (access_db, e))
-	    except ValueError:
-		raise service_error(service_error.internal, "%s" % e)
-	elif allow_any:
-	    auth.set_global_attribute("split")
-
-
-	# Dispatch tables
-	self.soap_services = {\
-		'Ns2Split': soap_handler("Ns2Split", self.run_splitter),
-	}
-
-	self.xmlrpc_services = {\
-		'Ns2Split': xmlrpc_handler('Ns2Split', self.run_splitter),
-	}
-
-    def run_splitter(self, req, fid):
-	"""
-	The external interface to experiment creation called from the
-	dispatcher.
-
-	Creates a working directory, splits the incoming description using the
-	splitter script and parses out the avrious subsections using the
-	lcasses above.  Once each sub-experiment is created, use pooled threads
-	to instantiate them and start it all up.
-	"""
-
-	if not self.auth.check_attribute(fid, 'split'):
-	    raise service_error(service_error.access, "Access Denied")
-
-	try:
-	    tmpdir = tempfile.mkdtemp(prefix="split-")
-	except IOError:
-	    raise service_error(service_error.internal, "Cannot create tmp dir")
-
-	tclfile = tmpdir + "/experiment.tcl"
-	pid = "dummy"
-	gid = "dummy"
-	eid = "dummy"
-
-	req = req.get('Ns2SplitRequestBody', None)
-	if not req:
-	    raise service_error(service_error.req,
-		    "Bad request format (no Ns2SplitRequestBody)")
-	# The tcl parser needs to read a file so put the content into that file
-	descr=req.get('description', None)
-	if descr:
-	    file_content=descr.get('ns2description', None)
-	    if file_content:
-		try:
-		    f = open(tclfile, 'w')
-		    f.write(file_content)
-		    f.close()
-		except IOError:
-		    raise service_error(service_error.internal,
-			    "Cannot write temp experiment description")
-	    else:
-		raise service_error(service_error.req, 
-			"Only ns2descriptions supported")
-	else:
-	    raise service_error(service_error.req, "No description")
-
-	master = req.get('master', None)
-	if master == None:
-	    raise service_error(service_error.req, "No master testbed label")
-	
-	include_fedkit = req.get('include_fedkit', None)
-	if include_fedkit == None:
-	    raise service_error(service_error.req, 
-		    "No federation kit inclusion directive")
-	
-	
-	tclcmd = [self.tclsh, self.tcl_splitter, '-s', '-x', 
-	    str(self.muxmax), '-m', master]
-	if include_fedkit: tclcmd.append('-k')
-	tclcmd.extend([pid, gid, eid, tclfile])
-	self.log.debug("Calling splitter %s" % " ".join(tclcmd))
-	tclparser = subprocess.Popen(tclcmd, stdout=subprocess.PIPE)
-
-	out = ""
-	# Package up the split data
-	for line in tclparser.stdout:
-	    out += line
-
-	# Walk up tmpdir, deleting as we go
-	for path, dirs, files in os.walk(tmpdir, topdown=False):
-	    for f in files:
-		os.remove(os.path.join(path, f))
-	    for d in dirs:
-		os.rmdir(os.path.join(path, d))
-	os.rmdir(tmpdir)
-
-	resp = { 'output' : out }
-
-	return resp
-
-
Index: fedd/fedd/util.py
===================================================================
--- fedd/fedd/util.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ 	(revision )
@@ -1,116 +1,0 @@
-#!/usr/local/bin/python
-
-import re
-import string
-import logging
-
-from M2Crypto import SSL
-from fedid import fedid
-
-class fedd_ssl_context(SSL.Context):
-    """
-    Simple wrapper around an M2Crypto.SSL.Context to initialize it for fedd.
-    """
-    def __init__(self, my_cert, trusted_certs=None, password=None):
-	"""
-	construct a fedd_ssl_context
-
-	@param my_cert: PEM file with my certificate in it
-	@param trusted_certs: PEM file with trusted certs in it (optional)
-	"""
-	SSL.Context.__init__(self)
-
-	# load_cert takes a callback to get a password, not a password, so if
-	# the caller provided a password, this creates a nonce callback using a
-	# lambda form.
-	if password != None and not callable(password):
-	    # This is cute.  password = lambda *args: password produces a
-	    # function object that returns itself rather than one that returns
-	    # the object itself.  This is because password is an object
-	    # reference and after the assignment it's a lambda.  So we assign
-	    # to a temp.
-	    pwd = password
-	    password =lambda *args: pwd
-
-	if password != None:
-	    self.load_cert(my_cert, callback=password)
-	else:
-	    self.load_cert(my_cert)
-
-	# If no trusted certificates are specified, allow unknown CAs.
-	if trusted_certs: 
-	    self.load_verify_locations(trusted_certs)
-	    self.set_verify(SSL.verify_peer, 10)
-	else:
-	    self.set_verify(SSL.verify_peer, 10, 
-		callback=SSL.cb.ssl_verify_callback_allow_unknown_ca)
-
-def read_simple_accessdb(fn, auth, mask=[]):
-    """
-    Read a simple access database.  Each line is a fedid (of the form
-    fedid:hexstring) and a comma separated list of atributes to be assigned to
-    it.  This parses out the fedids and adds the attributes to the authorizer.
-    comments (preceded with a #) and blank lines are ignored.  Exceptions (e.g.
-    file exceptions and ValueErrors from badly parsed lines) are propagated.
-    """
-
-    rv = [ ]
-    lineno = 0
-    fedid_line = re.compile("fedid:([" + string.hexdigits + "]+)\s+" +\
-	    "(\w+\s*(,\s*\w+)*)")
-
-    # If a single string came in, make it a list
-    if isinstance(mask, basestring): mask = [ mask ]
-
-    f = open(fn, 'r')
-    for line in f:
-	lineno += 1
-	line = line.strip()
-	if line.startswith('#') or len(line) == 0: 
-	    continue
-	m = fedid_line.match(line)
-	if m :
-	    fid = fedid(hexstr=m.group(1))
-	    for a in [ a.strip() for a in m.group(2).split(",") \
-		    if not mask or a.strip() in mask ]:
-		auth.set_attribute(fid, a.strip())
-	else:
-	    raise ValueError("Badly formatted line in accessdb: %s line %d" %\
-		    (nf, lineno))
-    f.close()
-    return rv
-	
-
-def pack_id(id):
-    """
-    Return a dictionary with the field name set by the id type.  Handy for
-    creating dictionaries to be converted to messages.
-    """
-    if isinstance(id, fedid): return { 'fedid': id }
-    elif id.startswith("http:") or id.startswith("https:"): return { 'uri': id }
-    else: return { 'localname': id}
-
-def unpack_id(id):
-    """return id as a type determined by the key"""
-    for k in ("localname", "fedid", "uri", "kerberosUsername"):
-	if id.has_key(k): return id[k]
-    return None
-
-def set_log_level(config, sect, log):
-    """ Set the logging level to the value passed in sect of config."""
-    # The getattr sleight of hand finds the logging level constant
-    # corrersponding to the string.  We're a little paranoid to avoid user
-    # mayhem.
-    if config.has_option(sect, "log_level"):
-	level_str = config.get(sect, "log_level")
-	try:
-	    level = int(getattr(logging, level_str.upper(), -1))
-
-	    if  logging.DEBUG <= level <= logging.CRITICAL:
-		log.setLevel(level)
-	    else:
-		log.error("Bad experiment_log value: %s" % level_str)
-
-	except ValueError:
-	    log.error("Bad experiment_log value: %s" % level_str)
-
Index: fedd/fedd_client.py
===================================================================
--- fedd/fedd_client.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/fedd_client.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -5,7 +5,7 @@
 import pwd
 
-from fedd import fedid, service_error
-from fedd.util import fedd_ssl_context, pack_id, unpack_id
-from fedd.remote_service import service_caller
+from federation import fedid, service_error
+from federation.util import fedd_ssl_context, pack_id, unpack_id
+from federation.remote_service import service_caller
 
 from optparse import OptionParser, OptionValueError
Index: fedd/federation/__init__.py
===================================================================
--- fedd/federation/__init__.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/__init__.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,7 @@
+#/usr/local/bin/python
+
+from fedid import fedid
+from service_error import service_error
+from config_parser import config_parser
+
+import parse_detail
Index: fedd/federation/access.py
===================================================================
--- fedd/federation/access.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/access.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,784 @@
+#!/usr/local/bin/python
+
+import os,sys
+import re
+import string
+import copy
+import pickle
+import logging
+
+from threading import *
+
+from util import *
+from allocate_project import allocate_project_local, allocate_project_remote
+from access_project import access_project
+from fedid import fedid, generate_fedid
+from authorizer import authorizer
+from service_error import service_error
+from remote_service import xmlrpc_handler, soap_handler, service_caller
+
+
+# Make log messages disappear if noone configures a fedd logger
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.access")
+fl.addHandler(nullHandler())
+
+class access:
+    """
+    The implementation of access control based on mapping users to projects.
+
+    Users can be mapped to existing projects or have projects created
+    dynamically.  This implements both direct requests and proxies.
+    """
+
+    class parse_error(RuntimeError): pass
+
+    bool_attrs = ("project_priority", "allow_proxy")
+    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
+    id_attrs = ("testbed", "proxy",
+	    "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
+	    "project_allocation_uri", "project_allocation_cert_file", 
+	    "project_allocation_cert_pwd", "project_allocation_trusted_certs")
+    id_list_attrs = ("restricted",)
+
+    proxy_RequestAccess= service_caller('RequestAccess')
+    proxy_ReleaseAccess= service_caller('ReleaseAccess')
+
+    def __init__(self, config=None, auth=None):
+	"""
+	Initializer.  Pulls parameters out of the ConfigParser's access section.
+	"""
+
+	# Make sure that the configuration is in place
+	if config: 
+	    if not config.has_section("access"):
+		config.add_section("access")
+	    if not config.has_section("globals"):
+		config.add_section("globals")
+	else:
+	    raise RunTimeError("No config to fedd.access")
+
+	# Create instance attributes from the static lists
+	for a in access.bool_attrs:
+	    if config.has_option("access", a):
+		setattr(self, a, config.get("access", a))
+	    else:
+		setattr(self, a, False)
+
+	for a in access.emulab_attrs + access.id_attrs:
+	    if config.has_option("access", a):
+		setattr(self, a, config.get("access",a))
+	    else:
+		setattr(self, a, None)
+
+	self.attrs = { }
+	self.access = { }
+	self.restricted = [ ]
+	self.fedid_category = { }
+	self.projects = { }
+	self.keys = { }
+	self.allocation = { }
+	self.state = { 
+	    'projects': self.projects,
+	    'allocation' : self.allocation,
+	    'keys' : self.keys
+	}
+	self.log = logging.getLogger("fedd.access")
+	set_log_level(config, "access", self.log)
+	self.state_lock = Lock()
+
+	if auth: self.auth = auth
+	else:
+	    self.log.error(\
+		    "[access]: No authorizer initialized, creating local one.")
+	    auth = authorizer()
+
+	self.fedid_default = "testbed"
+	if config.has_option("access", "accessdb"):
+	    self.read_access(config.get("access", "accessdb"))
+
+	self.state_filename = config.get("access", "access_state", "")
+	self.read_state()
+	# Certs are promoted from the generic to the specific, so without a
+	# specific proxy certificate, the main certificates are used for
+	# proxy interactions. If no dynamic project certificates, then
+	# proxy certs are used, and if none of those the main certs.
+
+	if config.has_option("globals", "proxy_cert_file"):
+	    if not self.project_allocation_cert_file:
+		self.project_allocation_cert_file = \
+			config.get("globals", "proxy_cert_file")
+		if config.has_option("globals", "proxy_cert_pwd"):
+		    self.project_allocation_cert_pwd = \
+			    config.get("globals", "proxy_cert_pwd")
+
+	if config.has_option("globals", "proxy_trusted_certs"):
+	    if not self.project_allocation_trusted_certs:
+		self.project_allocation_trusted_certs =\
+			config.get("globals", proxy_trusted_certs)
+
+	if config.has_option("globals", "cert_file"):
+	    has_pwd = config.has_option("globals", "cert_pwd")
+	    if not self.project_allocation_cert_file:
+		self.project_allocation_cert_file = \
+			config.get("globals", "cert_file")
+		if has_pwd: 
+		    self.project_allocation_cert_pwd = \
+			    config.get("globals", "cert_pwd")
+	    if not self.proxy_cert_file:
+		self.proxy_cert_file = config.get("globals", "cert_file")
+		if has_pwd:
+		    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
+
+	if config.get("globals", "trusted_certs"):
+	    if not self.proxy_trusted_certs:
+		self.proxy_trusted_certs = \
+			config.get("globals", "trusted_certs")
+	    if not self.project_allocation_trusted_certs:
+		self.project_allocation_trusted_certs = \
+			config.get("globals", "trusted_certs")
+
+	proj_certs = (self.project_allocation_cert_file, 
+		self.project_allocation_trusted_certs,
+		self.project_allocation_cert_pwd)
+
+	self.soap_services = {\
+	    'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
+	    'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
+	    }
+	self.xmlrpc_services =  {\
+	    'RequestAccess': xmlrpc_handler('RequestAccess',
+		self.RequestAccess),
+	    'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
+		self.ReleaseAccess),
+	    }
+
+
+	if not config.has_option("allocate", "uri"):
+	    self.allocate_project = \
+		allocate_project_local(config, auth)
+	else:
+	    self.allocate_project = \
+		allocate_project_remote(config, auth)
+
+	# If the project allocator exports services, put them in this object's
+	# maps so that classes that instantiate this can call the services.
+	self.soap_services.update(self.allocate_project.soap_services)
+	self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
+
+
+    def read_access(self, config):
+	"""
+	Read a configuration file and set internal parameters.
+
+	The format is more complex than one might hope.  The basic format is
+	attribute value pairs separated by colons(:) on a signle line.  The
+	attributes in bool_attrs, emulab_attrs and id_attrs can all be set
+	directly using the name: value syntax.  E.g. 
+	boss: hostname
+	sets self.boss to hostname.  In addition, there are access lines of the
+	form (tb, proj, user) -> (aproj, auser) that map the first tuple of
+	names to the second for access purposes.  Names in the key (left side)
+	can include "<NONE> or <ANY>" to act as wildcards or to require the
+	fields to be empty.  Similarly aproj or auser can be <SAME> or
+	<DYNAMIC> indicating that either the matching key is to be used or a
+	dynamic user or project will be created.  These names can also be
+	federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
+	can be followed with a colon-separated list of node types to which that
+	project has access (or will have access if dynamic).
+	Testbed attributes outside the forms above can be given using the
+	format attribute: name value: value.  The name is a single word and the
+	value continues to the end of the line.  Empty lines and lines startin
+	with a # are ignored.
+
+	Parsing errors result in a self.parse_error exception being raised.
+	"""
+	lineno=0
+	name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
+	fedid_expr = "fedid:[" + string.hexdigits + "]+"
+	key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
+	access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
+		"<SAME>" + "(?::" + name_expr + ")*|" + \
+		fedid_expr + "(?::" + name_expr + ")*|" + \
+		name_expr + "(?::" + name_expr + ")*)"
+	access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
+
+	restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
+	attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
+		re.IGNORECASE)
+	access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
+		key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
+		access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
+
+	def parse_name(n):
+	    if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
+	    else: return n
+	
+	def auth_name(n):
+	    if isinstance(n, basestring):
+		if n =='<any>' or n =='<none>': return None
+		else: return unicode(n)
+	    else:
+		return n
+
+	f = open(config, "r");
+	for line in f:
+	    lineno += 1
+	    line = line.strip();
+	    if len(line) == 0 or line.startswith('#'):
+		continue
+
+	    # Extended (attribute: x value: y) attribute line
+	    m = attr_re.match(line)
+	    if m != None:
+		attr, val = m.group(1,2)
+		self.attrs[attr] = val
+		continue
+
+	    # Restricted entry 
+	    m = restricted_re.match(line)
+	    if m != None:
+		val = m.group(1)
+		self.restricted.append(val)
+		continue
+
+	    # Access line (t, p, u) -> (ap, cu, su) line
+	    m = access_re.match(line)
+	    if m != None:
+		access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
+		auth_key = tuple([ auth_name(x) for x in access_key])
+		aps = m.group(4).split(":");
+		if aps[0] == 'fedid:':
+		    del aps[0]
+		    aps[0] = fedid(hexstr=aps[0])
+
+		cu = parse_name(m.group(5))
+		su = parse_name(m.group(6))
+
+		access_val = (access_project(aps[0], aps[1:]),
+			parse_name(m.group(5)), parse_name(m.group(6)))
+
+		self.access[access_key] = access_val
+		self.auth.set_attribute(auth_key, "access")
+		continue
+
+	    # Nothing matched to here: unknown line - raise exception
+	    f.close()
+	    raise self.parse_error("Unknown statement at line %d of %s" % \
+		    (lineno, config))
+	f.close()
+
+
+    def dump_state(self):
+	"""
+	Dump the state read from a configuration file.  Mostly for debugging.
+	"""
+	for a in access.bool_attrs:
+	    print "%s: %s" % (a, getattr(self, a ))
+	for a in access.emulab_attrs + access.id_attrs:
+	    print "%s: %s" % (a, getattr(self, a))
+	for k, v in self.attrs.iteritems():
+	    print "%s %s" % (k, v)
+	print "Access DB:"
+	for k, v in self.access.iteritems():
+	    print "%s %s" % (k, v)
+	print "Trust DB:"
+	for k, v in self.fedid_category.iteritems():
+	    print "%s %s" % (k, v)
+	print "Restricted: %s" % str(',').join(sorted(self.restricted))
+
+    def get_users(self, obj):
+	"""
+	Return a list of the IDs of the users in dict
+	"""
+	if obj.has_key('user'):
+	    return [ unpack_id(u['userID']) \
+		    for u in obj['user'] if u.has_key('userID') ]
+	else:
+	    return None
+
+    def write_state(self):
+	try:
+	    f = open(self.state_filename, 'w')
+	    pickle.dump(self.state, f)
+	except IOError, e:
+	    self.log.error("Can't write file %s: %s" % \
+		    (self.state_filename, e))
+	except pickle.PicklingError, e:
+	    self.log.error("Pickling problem: %s" % e)
+	except TypeError, e:
+	    self.log.error("Pickling problem (TypeError): %s" % e)
+
+
+    def read_state(self):
+	"""
+	Read a new copy of access state.  Old state is overwritten.
+
+	State format is a simple pickling of the state dictionary.
+	"""
+	try:
+	    f = open(self.state_filename, "r")
+	    self.state = pickle.load(f)
+
+	    self.allocation = self.state['allocation']
+	    self.projects = self.state['projects']
+	    self.keys = self.state['keys']
+
+	    self.log.debug("[read_state]: Read state from %s" % \
+		    self.state_filename)
+	except IOError, e:
+	    self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
+		    % (self.state_filename, e))
+	except EOFError, e:
+	    self.log.warning("[read_state]: Empty or damaged state file: %s:"\
+		    % self.state_filename)
+	except pickle.UnpicklingError, e:
+	    self.log.warning(("[read_state]: No saved state: " + \
+		    "Unpickling failed: %s") % e)
+
+	# Add the ownership attributes to the authorizer.  Note that the
+	# indices of the allocation dict are strings, but the attributes are
+	# fedids, so there is a conversion.
+	for k in self.allocation.keys():
+	    for o in self.allocation[k].get('owners', []):
+		self.auth.set_attribute(o, fedid(hexstr=k))
+
+
+    def permute_wildcards(self, a, p):
+	"""Return a copy of a with various fields wildcarded.
+
+	The bits of p control the wildcards.  A set bit is a wildcard
+	replacement with the lowest bit being user then project then testbed.
+	"""
+	if p & 1: user = ["<any>"]
+	else: user = a[2]
+	if p & 2: proj = "<any>"
+	else: proj = a[1]
+	if p & 4: tb = "<any>"
+	else: tb = a[0]
+
+	return (tb, proj, user)
+
+    def find_access(self, search):
+	"""
+	Search the access DB for a match on this tuple.  Return the matching
+	access tuple and the user that matched.
+	
+	NB, if the initial tuple fails to match we start inserting wildcards in
+	an order determined by self.project_priority.  Try the list of users in
+	order (when wildcarded, there's only one user in the list).
+	"""
+	if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
+	else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
+
+	for p in perm: 
+	    s = self.permute_wildcards(search, p)
+	    # s[2] is None on an anonymous, unwildcarded request
+	    if s[2] != None:
+		for u in s[2]:
+		    if self.access.has_key((s[0], s[1], u)):
+			return (self.access[(s[0], s[1], u)], u)
+	    else:
+		if self.access.has_key(s):
+		    return (self.access[s], None)
+	return None, None
+
+    def lookup_access(self, req, fid):
+	"""
+	Determine the allowed access for this request.  Return the access and
+	which fields are dynamic.
+
+	The fedid is needed to construct the request
+	"""
+	# Search keys
+	tb = None
+	project = None
+	user = None
+	# Return values
+	rp = access_project(None, ())
+	ru = None
+
+	if req.has_key('project'):
+	    p = req['project']
+	    if p.has_key('name'):
+		project = unpack_id(p['name'])
+	    user = self.get_users(p)
+	else:
+	    user = self.get_users(req)
+
+	user_fedids = [ u for u in user if isinstance(u, fedid)]
+	# Determine how the caller is representing itself.  If its fedid shows
+	# up as a project or a singleton user, let that stand.  If neither the
+	# usernames nor the project name is a fedid, the caller is a testbed.
+	if project and isinstance(project, fedid):
+	    if project == fid:
+		# The caller is the project (which is already in the tuple
+		# passed in to the authorizer)
+		owners = user_fedids
+		owners.append(project)
+	    else:
+		raise service_error(service_error.req,
+			"Project asserting different fedid")
+	else:
+	    if fid not in user_fedids:
+		tb = fid
+		owners = user_fedids
+		owners.append(fid)
+	    else:
+		if len(fedids) > 1:
+		    raise service_error(service_error.req,
+			    "User asserting different fedid")
+		else:
+		    # Which is a singleton
+		    owners = user_fedids
+	# Confirm authorization
+	for u in user:
+	    self.log.debug("[lookup_access] Checking access for %s" % \
+		    ((tb, project, u),))
+	    if self.auth.check_attribute((tb, project, u), 'access'):
+		self.log.debug("[lookup_access] Access granted")
+		break
+	    else:
+		self.log.debug("[lookup_access] Access Denied")
+	else:
+	    raise service_error(service_error.access, "Access denied")
+
+	# This maps a valid user to the Emulab projects and users to use
+	found, user_match = self.find_access((tb, project, user))
+	
+	if found == None:
+	    raise service_error(service_error.access,
+		    "Access denied - cannot map access")
+
+	# resolve <dynamic> and <same> in found
+	dyn_proj = False
+	dyn_create_user = False
+	dyn_service_user = False
+
+	if found[0].name == "<same>":
+	    if project != None:
+		rp.name = project
+	    else : 
+		raise service_error(\
+			service_error.server_config,
+			"Project matched <same> when no project given")
+	elif found[0].name == "<dynamic>":
+	    rp.name = None
+	    dyn_proj = True
+	else:
+	    rp.name = found[0].name
+	rp.node_types = found[0].node_types;
+
+	if found[1] == "<same>":
+	    if user_match == "<any>":
+		if user != None: rcu = user[0]
+		else: raise service_error(\
+			service_error.server_config,
+			"Matched <same> on anonymous request")
+	    else:
+		rcu = user_match
+	elif found[1] == "<dynamic>":
+	    rcu = None
+	    dyn_create_user = True
+	
+	if found[2] == "<same>":
+	    if user_match == "<any>":
+		if user != None: rsu = user[0]
+		else: raise service_error(\
+			service_error.server_config,
+			"Matched <same> on anonymous request")
+	    else:
+		rsu = user_match
+	elif found[2] == "<dynamic>":
+	    rsu = None
+	    dyn_service_user = True
+
+	return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
+		owners
+
+    def build_response(self, alloc_id, ap):
+	"""
+	Create the SOAP response.
+
+	Build the dictionary description of the response and use
+	fedd_utils.pack_soap to create the soap message.  ap is the allocate
+	project message returned from a remote project allocation (even if that
+	allocation was done locally).
+	"""
+	# Because alloc_id is already a fedd_services_types.IDType_Holder,
+	# there's no need to repack it
+	msg = { 
+		'allocID': alloc_id,
+		'emulab': { 
+		    'domain': self.domain,
+		    'boss': self.boss,
+		    'ops': self.ops,
+		    'fileServer': self.fileserver,
+		    'eventServer': self.eventserver,
+		    'project': ap['project']
+		},
+	    }
+	if len(self.attrs) > 0:
+	    msg['emulab']['fedAttr'] = \
+		[ { 'attribute': x, 'value' : y } \
+			for x,y in self.attrs.iteritems()]
+	return msg
+
+    def RequestAccess(self, req, fid):
+	"""
+	Handle the access request.  Proxy if not for us.
+
+	Parse out the fields and make the allocations or rejections if for us,
+	otherwise, assuming we're willing to proxy, proxy the request out.
+	"""
+
+	# The dance to get into the request body
+	if req.has_key('RequestAccessRequestBody'):
+	    req = req['RequestAccessRequestBody']
+	else:
+	    raise service_error(service_error.req, "No request!?")
+
+	if req.has_key('destinationTestbed'):
+	    dt = unpack_id(req['destinationTestbed'])
+
+	if dt == None or dt == self.testbed:
+	    # Request for this fedd
+	    found, dyn, owners = self.lookup_access(req, fid)
+	    restricted = None
+	    ap = None
+
+	    # If this is a request to export a project and the access project
+	    # is not the project to export, access denied.
+	    if req.has_key('exportProject'):
+		ep = unpack_id(req['exportProject'])
+		if ep != found[0].name:
+		    raise service_error(service_error.access,
+			    "Cannot export %s" % ep)
+
+	    # Check for access to restricted nodes
+	    if req.has_key('resources') and req['resources'].has_key('node'):
+		resources = req['resources']
+		restricted = [ t for n in resources['node'] \
+				if n.has_key('hardware') \
+				    for t in n['hardware'] \
+					if t in self.restricted ]
+		inaccessible = [ t for t in restricted \
+				    if t not in found[0].node_types]
+		if len(inaccessible) > 0:
+		    raise service_error(service_error.access,
+			    "Access denied (nodetypes %s)" % \
+			    str(', ').join(inaccessible))
+	    # These collect the keys for the two roles into single sets, one
+	    # for creation and one for service.  The sets are a simple way to
+	    # eliminate duplicates
+	    create_ssh = set([ x['sshPubkey'] \
+		    for x in req['createAccess'] \
+			if x.has_key('sshPubkey')])
+
+	    service_ssh = set([ x['sshPubkey'] \
+		    for x in req['serviceAccess'] \
+			if x.has_key('sshPubkey')])
+
+	    if len(create_ssh) > 0 and len(service_ssh) >0: 
+		if dyn[1]: 
+		    # Compose the dynamic project request 
+		    # (only dynamic, dynamic currently allowed)
+		    preq = { 'AllocateProjectRequestBody': \
+				{ 'project' : {\
+				    'user': [ \
+				    { \
+					'access': [ { 'sshPubkey': s } \
+					    for s in service_ssh ], 
+					 'role': "serviceAccess",\
+				    }, \
+				    { \
+					'access': [ { 'sshPubkey': s } \
+					    for s in create_ssh ], 
+					 'role': "experimentCreation",\
+				    }, \
+				    ], \
+				    }\
+				}\
+			    }
+		    if restricted != None and len(restricted) > 0:
+			preq['AllocateProjectRequestBody']['resources'] = \
+			    [ {'node': { 'hardware' :  [ h ] } } \
+				    for h in restricted ]
+		    ap = self.allocate_project.dynamic_project(preq)
+		else:
+		    preq = {'StaticProjectRequestBody' : \
+			    { 'project': \
+				{ 'name' : { 'localname' : found[0].name },\
+				  'user' : [ \
+				    {\
+					'userID': { 'localname' : found[1] }, \
+					'access': [ { 'sshPubkey': s } 
+					    for s in create_ssh ],
+					'role': 'experimentCreation'\
+				    },\
+				    {\
+					'userID': { 'localname' : found[2] }, \
+					'access': [ { 'sshPubkey': s } 
+					    for s in service_ssh ],
+					'role': 'serviceAccess'\
+				    },\
+				]}\
+			    }\
+		    }
+		    if restricted != None and len(restricted) > 0:
+			preq['StaticProjectRequestBody']['resources'] = \
+			    [ {'node': { 'hardware' :  [ h ] } } \
+				    for h in restricted ]
+		    ap = self.allocate_project.static_project(preq)
+	    else:
+		raise service_error(service_error.req, 
+			"SSH access parameters required")
+	    # keep track of what's been added
+	    allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
+	    aid = unicode(allocID)
+
+	    self.state_lock.acquire()
+	    self.allocation[aid] = { }
+	    if dyn[1]:
+		try:
+		    pname = ap['project']['name']['localname']
+		except KeyError:
+		    self.state_lock.release()
+		    raise service_error(service_error.internal,
+			    "Misformed allocation response?")
+		if self.projects.has_key(pname): self.projects[pname] += 1
+		else: self.projects[pname] = 1
+		self.allocation[aid]['project'] = pname
+
+	    self.allocation[aid]['keys'] = [ ]
+
+	    try:
+		for u in ap['project']['user']:
+		    uname = u['userID']['localname']
+		    for k in [ k['sshPubkey'] for k in u['access'] \
+			    if k.has_key('sshPubkey') ]:
+			kv = "%s:%s" % (uname, k)
+			if self.keys.has_key(kv): self.keys[kv] += 1
+			else: self.keys[kv] = 1
+			self.allocation[aid]['keys'].append((uname, k))
+	    except KeyError:
+		self.state_lock.release()
+		raise service_error(service_error.internal,
+			"Misformed allocation response?")
+
+	    self.allocation[aid]['owners'] = owners
+	    self.write_state()
+	    self.state_lock.release()
+	    for o in owners:
+		self.auth.set_attribute(o, allocID)
+	    resp = self.build_response({ 'fedid': allocID } , ap)
+	    return resp
+	else:
+	    if self.allow_proxy:
+		resp = self.proxy_RequestAccess.call_service(dt, req,
+			    self.proxy_cert_file, self.proxy_cert_pwd,
+			    self.proxy_trusted_certs)
+		if resp.has_key('RequestAccessResponseBody'):
+		    return resp['RequestAccessResponseBody']
+		else:
+		    return None
+	    else:
+		raise service_error(service_error.access,
+			"Access proxying denied")
+
+    def ReleaseAccess(self, req, fid):
+	# The dance to get into the request body
+	if req.has_key('ReleaseAccessRequestBody'):
+	    req = req['ReleaseAccessRequestBody']
+	else:
+	    raise service_error(service_error.req, "No request!?")
+
+	if req.has_key('destinationTestbed'):
+	    dt = unpack_id(req['destinationTestbed'])
+	else:
+	    dt = None
+
+	if dt == None or dt == self.testbed:
+	    # Local request
+	    try:
+		if req['allocID'].has_key('localname'):
+		    auth_attr = aid = req['allocID']['localname']
+		elif req['allocID'].has_key('fedid'):
+		    aid = unicode(req['allocID']['fedid'])
+		    auth_attr = req['allocID']['fedid']
+		else:
+		    raise service_error(service_error.req,
+			    "Only localnames and fedids are understood")
+	    except KeyError:
+		raise service_error(service_error.req, "Badly formed request")
+
+	    if not self.auth.check_attribute(fid, auth_attr):
+		raise service_error(service_error.access, "Access Denied")
+
+	    # If we know this allocation, reduce the reference counts and
+	    # remove the local allocations.  Otherwise report an error.  If
+	    # there is an allocation to delete, del_users will be a dictonary
+	    # of sets where the key is the user that owns the keys in the set.
+	    # We use a set to avoid duplicates.  del_project is just the name
+	    # of any dynamic project to delete.  We're somewhat lazy about
+	    # deleting authorization attributes.  Having access to something
+	    # that doesn't exist isn't harmful.
+	    del_users = { }
+	    del_project = None
+	    if self.allocation.has_key(aid):
+		self.state_lock.acquire()
+		for k in self.allocation[aid]['keys']:
+		    kk = "%s:%s" % k
+		    self.keys[kk] -= 1
+		    if self.keys[kk] == 0:
+			if not del_users.has_key(k[0]):
+			    del_users[k[0]] = set()
+			del_users[k[0]].add(k[1])
+			del self.keys[kk]
+
+		if self.allocation[aid].has_key('project'):
+		    pname = self.allocation[aid]['project']
+		    self.projects[pname] -= 1
+		    if self.projects[pname] == 0:
+			del_project = pname
+			del self.projects[pname]
+
+		del self.allocation[aid]
+		self.write_state()
+		self.state_lock.release()
+		# If we actually have resources to deallocate, prepare the call.
+		if del_project or del_users:
+		    msg = { 'project': { }}
+		    if del_project:
+			msg['project']['name']= {'localname': del_project}
+		    users = [ ]
+		    for u in del_users.keys():
+			users.append({ 'userID': { 'localname': u },\
+			    'access' :  \
+				    [ {'sshPubkey' : s } for s in del_users[u]]\
+			})
+		    if users: 
+			msg['project']['user'] = users
+		    if self.allocate_project.release_project:
+			msg = { 'ReleaseProjectRequestBody' : msg}
+			self.allocate_project.release_project(msg)
+		return { 'allocID': req['allocID'] } 
+	    else:
+		raise service_error(service_error.req, "No such allocation")
+
+	else:
+	    if self.allow_proxy:
+		resp = self.proxy_ReleaseAccess.call_service(dt, req,
+			    self.proxy_cert_file, self.proxy_cert_pwd,
+			    self.proxy_trusted_certs)
+		if resp.has_key('ReleaseAccessResponseBody'):
+		    return resp['ReleaseAccessResponseBody']
+		else:
+		    return None
+	    else:
+		raise service_error(service_error.access,
+			"Access proxying denied")
+
+
Index: fedd/federation/access_project.py
===================================================================
--- fedd/federation/access_project.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/access_project.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,20 @@
+#!/usr/local/bin/python
+
+class access_project:
+    """
+    A project description used to grant access to this testbed.
+
+    The description includes a name and a list of node types to which the
+    project will be granted access.
+    """
+    def __init__(self, name, nt):
+	self.name = name
+	self.node_types = list(nt)
+
+    def __repr__(self):
+	if len(self.node_types) > 0:
+	    return "access_proj('%s', ['%s'])" % \
+		    (self.name, str("','").join(self.node_types))
+	else:
+	    return "access_proj('%s', [])" % self.name
+
Index: fedd/federation/allocate_project.py
===================================================================
--- fedd/federation/allocate_project.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/allocate_project.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,517 @@
+#!/usr/local/bin/python
+
+import os,sys
+import re
+import random
+import string
+import subprocess
+import tempfile
+
+from util import *
+from fedid import fedid
+from fixed_resource import read_key_db, read_project_db
+from remote_service import xmlrpc_handler, soap_handler, service_caller
+from service_error import service_error
+import logging
+
+
+# Configure loggers to dump to /dev/null which avoids errors if calling classes
+# don't configure them.
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.allocate.local")
+fl.addHandler(nullHandler())
+fl = logging.getLogger("fedd.allocate.remote")
+fl.addHandler(nullHandler())
+
+
+class allocate_project_local:
+    """
+    Allocate projects on this machine in response to an access request.
+    """
+    dynamic_projects = 4
+    dynamic_keys= 2
+    confirm_keys = 1
+    none = 0
+
+    levels = {
+	    'dynamic_projects': dynamic_projects,
+	    'dynamic_keys': dynamic_keys,
+	    'confirm_keys': confirm_keys,
+	    'none': none,
+    }
+
+    def __init__(self, config, auth=None):
+	"""
+	Initializer.  Parses a configuration if one is given.
+	"""
+
+	self.debug = config.get("allocate", "debug", False)
+	self.wap = config.get('allocate', 'wap', '/usr/testbed/sbin/wap')
+	self.newproj = config.get('allocate', 'newproj',
+		'/usr/testbed/sbin/newproj')
+	self.mkproj = config.get('allocate', 'mkproj', 
+		'/usr/testbed/sbin/mkproj')
+	self.rmproj = config.get('allocate', 'rmproj',
+		'/usr/testbed/sbin/rmproj')
+	self.addpubkey = config.get('allocate', 'addpubkey', 
+		'/usr/testbed/sbin/taddpubkey')
+	self.grantnodetype = config.get('allocate', 'grantnodetype', 
+		'/usr/testbed/sbin/grantnodetype')
+	self.confirmkey = config.get('allocate', 'confirmkey', 
+		'/usr/testbed/sbin/taddpubkey')
+	self.allocation_level = config.get("allocate", "allocation_level", 
+		"none")
+	self.log = logging.getLogger("fedd.allocate.local")
+	set_log_level(config, "allocate", self.log)
+
+	if auth:
+	    self.auth = auth
+	else:
+	    auth = authorizer()
+	    log.warn("[allocate] No authorizer passed in, using local one")
+
+	try:
+	    self.allocation_level = \
+		    self.levels[self.allocation_level.strip().lower()]
+	except KeyError:
+	    self.log.error("Bad allocation_level %s.  Defaulting to none" % \
+		    self.allocation_error)
+	    self.allocation_level = self.none
+
+	access_db = config.get("allocate", "accessdb")
+	if access_db:
+	    try:
+		read_simple_accessdb(access_db, self.auth, 'allocate')
+	    except IOError, e:
+		raise service_error(service_error.internal,
+			"Error reading accessDB %s: %s" % (access_db, e))
+	    except ValueError:
+		raise service_error(service_error.internal, "%s" % e)
+
+
+	fixed_key_db = config.get("allocate", "fixed_keys", None)
+	fixed_project_db = config.get("allocate", "fixed_projects", None)
+	self.fixed_keys = set()
+	self.fixed_projects = set()
+
+	# initialize the fixed resource sets
+	for db, rset, fcn in (\
+		(fixed_key_db, self.fixed_keys, read_key_db), \
+		(fixed_project_db, self.fixed_projects, read_project_db)):
+	    if db:
+		try:
+		    rset.update(fcn(db))
+		except:
+		    self.log.debug("Can't read resources from %s" % db)
+	
+	# Internal services are SOAP only
+	self.soap_services = {\
+		"AllocateProject": soap_handler("AllocateProject", 
+		    self.dynamic_project),
+		"StaticProject": soap_handler("StaticProject", 
+		    self.static_project),
+		"ReleaseProject": soap_handler("ReleaseProject", 
+		    self.release_project),
+		}
+	self.xmlrpc_services = { }
+
+    def random_string(self, s, n=3):
+	"""Append n random ASCII characters to s and return the string"""
+	rv = s
+	for i in range(0,n):
+	    rv += random.choice(string.ascii_letters)
+	return rv
+
+    def write_attr_xml(self, file, root, lines):
+	"""
+	Write an emulab config file for a dynamic project.
+
+	Format is <root><attribute name=lines[0]>lines[1]</attribute></root>
+	"""
+	# Convert a pair to an attribute line
+	out_attr = lambda a,v : \
+		'<attribute name="%s"><value>%s</value></attribute>' % (a, v)
+
+	f = os.fdopen(file, "w")
+	f.write("<%s>\n" % root)
+	f.write("\n".join([out_attr(*l) for l in lines]))
+	f.write("</%s>\n" % root)
+	f.close()
+
+
+    def dynamic_project(self, req, fedid=None):
+	"""
+	Create a dynamic project with ssh access
+
+	Req includes the project and resources as a dictionary
+	"""
+
+	# Internal calls do not have a fedid parameter (i.e., local calls on
+	# behalf of already vetted fedids)
+	if fedid and not self.auth.check_attribute(fedid, "allocate"):
+	    self.log.debug("[allocate] Access denied (%s)" % fedid)
+	    raise service_error(service_error.access, "Access Denied")
+
+	if self.allocation_level < self.dynamic_projects:
+	    raise service_error(service_error.access, 
+		    "[dynamic_project] dynamic project allocation not " + \
+			    "permitted: check allocation level")
+	# tempfiles for the parameter files
+	uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
+		dir="/tmp")
+	pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
+		dir="/tmp")
+
+	if req.has_key('AllocateProjectRequestBody') and \
+		req['AllocateProjectRequestBody'].has_key('project'):
+	    proj = req['AllocateProjectRequestBody']['project']
+	else:
+	    raise service_error(service_error.req, 
+		    "Badly formed allocation request")
+	# Take the first user and ssh key
+	name = proj.get('name', None) or self.random_string("proj",4)
+	user = proj.get('user', None)
+	if user != None:
+	    user = user[0]	# User is a list, take the first entry
+	    if not user.has_key("userID"):
+		uname = self.random_string("user", 3)
+	    else:
+		uid = proj['userID']
+		# XXX: fedid
+		uname = uid.get('localname', None) or \
+			uid.get('kerberosUsername', None) or \
+			uid.get('uri', None)
+		if uname == None:
+		    raise service_error(service_error.req, "No ID for user");
+
+	    access = user.get('access', None)
+	    if access != None:
+		ssh = access[0].get('sshPubkey', None)
+		if ssh == None:
+		    raise service_error(service_error.req, 
+			    "No ssh key for user");
+	else:
+	    raise service_error(service_error.req, 
+		    "No access information for project");
+
+	# uname, name and ssh are set
+	user_fields = [
+		("name", "Federation User %s" % uname),
+		("email", "%s-fed@isi.deterlab.net" % uname),
+		("password", self.random_string("", 8)),
+		("login", uname),
+		("address", "4676 Admiralty"),
+		("city", "Marina del Rey"),
+		("state", "CA"),
+		("zip", "90292"),
+		("country", "USA"),
+		("phone", "310-448-9190"),
+		("title", "None"),
+		("affiliation", "USC/ISI"),
+		("pubkey", ssh)
+	]
+
+	proj_fields = [
+		("name", name),
+		("short description", "dynamic federated project"),
+		("URL", "http://www.isi.edu/~faber"),
+		("funders", "USC/USU"),
+		("long description", "Federation access control"),
+		("public", "1"),
+		("num_pcs", "100"),
+		("linkedtous", "1"),
+		("newuser_xml", userfile)
+	]
+	
+
+	# Write out the files
+	self.write_attr_xml(uf, "user", user_fields)
+	self.write_attr_xml(pf, "project", proj_fields)
+
+	# Generate the commands (only grantnodetype's are dynamic)
+	cmds = [
+		(self.wap, self.newproj, projfile),
+		(self.wap, self.mkproj, name)
+		]
+
+	# Add commands to grant access to any resources in the request.
+	for nt in [ h for r in req.get('resources', []) \
+		if r.has_key('node') and r['node'].has_key('hardware')\
+		    for h in r['node']['hardware'] ] :
+	    cmds.append((self.wap, self.grantnodetype, '-p', name, nt))
+
+	# Create the projects
+	rc = 0
+	for cmd in cmds:
+	    self.log.debug("[dynamic_project]: %s" % ' '.join(cmd))
+	    if not self.debug:
+		try:
+		    rc = subprocess.call(cmd)
+		except OSerror, e:
+		    raise service_error(service_error.internal,
+			    "Dynamic project subprocess creation error "+ \
+				    "[%s] (%s)" %  (cmd[1], e.strerror))
+
+	    if rc != 0: 
+		raise service_error(service_error.internal,
+			"Dynamic project subprocess error " +\
+				"[%s] (%d)" % (cmd[1], rc))
+	# Clean up tempfiles
+	os.unlink(userfile)
+	os.unlink(projfile)
+	rv = {\
+	    'project': {\
+		'name': { 'localname': name }, 
+		'user' : [ {\
+		    'userID': { 'localname' : uname },
+		    'access': [ { 'sshPubkey' : ssh } ],
+		} ]\
+	    }\
+	}
+	return rv
+
+    def static_project(self, req, fedid=None):
+	"""
+	Be certain that the local project in the request has access to the
+	proper resources and users have correct keys.  Add them if necessary.
+	"""
+
+	cmds =  []
+
+	# Internal calls do not have a fedid parameter (i.e., local calls on
+	# behalf of already vetted fedids)
+	if fedid and not self.auth.check_attribute(fedid, "allocate"):
+	    self.log.debug("[allocate] Access denied (%s)" % fedid)
+	    raise service_error(service_error.access, "Access Denied")
+	# While we should be more careful about this, for the short term, add
+	# the keys to the specified users.
+
+	try:
+	    users = req['StaticProjectRequestBody']['project']['user']
+	    pname = req['StaticProjectRequestBody']['project']\
+		    ['name']['localname']
+	    resources = req['StaticProjectRequestBody'].get('resources', [])
+	except KeyError:
+	    raise service_error(service_error.req, "Badly formed request")
+
+
+	for u in users:
+	    try:
+		name = u['userID']['localname']
+	    except KeyError:
+		raise service_error(service_error.req, "Badly formed user")
+	    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
+		    if k.has_key('sshPubkey')]:
+		if self.allocation_level >= self.dynamic_keys:
+		    cmds.append((self.wap, self.addpubkey, '-w', \
+			    '-u', name, '-k', sk))
+		elif self.allocation_level >= self.confirm_keys:
+		    cmds.append((self.wap, self.confirmkey, '-C', \
+			    '-u', name, '-k', sk))
+		else:
+		    self.log.warning("[static_project] no checking of " + \
+			    "static keys")
+	
+
+	# Add commands to grant access to any resources in the request.  The
+	# list comprehension pulls out the hardware types in the node entries
+	# in the resources list.
+	for nt in [ h for r in resources \
+		if r.has_key('node') and r['node'].has_key('hardware')\
+		    for h in r['node']['hardware'] ] :
+	    if self.allocation_level >= self.confirm_keys:
+		cmds.append((self.wap, self.grantnodetype, '-p', pname, nt))
+
+	# Run the commands 
+	rc = 0
+	for cmd in cmds:
+	    self.log.debug("[static_project]: %s" % ' '.join(cmd))
+	    if not self.debug:
+		try:
+		    rc = subprocess.call(cmd)
+		except OSError, e:
+		    raise service_error(service_error.internal,
+			    "Static project subprocess creation error "+ \
+				    "[%s] (%s)" %  (cmd[0], e.strerror))
+
+	    if rc != 0: 
+		raise service_error(service_error.internal,
+			"Static project subprocess error " +\
+				"[%s] (%d)" % (cmd[0], rc))
+
+	return { 'project': req['StaticProjectRequestBody']['project']}
+
+    def release_project(self, req, fedid=None):
+	"""
+	Remove user keys from users and delete dynamic projects.
+
+	Only keys not in the set of fixed keys are deleted. and there are
+	similar protections for projects.
+	"""
+	# Internal calls do not have a fedid parameter (i.e., local calls on
+	# behalf of already vetted fedids)
+	if fedid and not self.auth.check_attribute(fedid, "allocate"):
+	    self.log.debug("[allocate] Access denied (%s)" % fedid)
+	    raise service_error(service_error.access, "Access Denied")
+
+	cmds = []
+	pname = None
+	users = []
+
+	try:
+	    if req['ReleaseProjectRequestBody']['project'].has_key('name'):
+		pname = req['ReleaseProjectRequestBody']['project']\
+			['name']['localname']
+	    if req['ReleaseProjectRequestBody']['project'].has_key('user'):
+		users = req['ReleaseProjectRequestBody']['project']['user']
+	except KeyError:
+	    raise service_error(service_error.req, "Badly formed request")
+
+	for u in users:
+	    try:
+		name = u['userID']['localname']
+	    except KeyError:
+		raise service_error(service_error.req, "Badly formed user")
+	    for sk in [ k['sshPubkey'] for k in u.get('access', []) \
+		    if k.has_key('sshPubkey')]:
+		if (name.rstrip(), sk.rstrip()) not in self.fixed_keys:
+		    if self.allocation_level >= self.dynamic_keys:
+			cmds.append((self.wap, self.addpubkey, '-R', '-w', \
+				'-u', name, '-k', sk))
+	if pname and pname not in self.fixed_projects and \
+		self.allocation_level >= self.dynamic_projects:
+	    cmds.append((self.wap, self.rmproj, pname))
+
+	# Run the commands 
+	rc = 0
+	for cmd in cmds:
+	    self.log.debug("[release_project]: %s" % ' '.join(cmd))
+	    if not self.debug:
+		try:
+		    rc = subprocess.call(cmd)
+		except OSError, e:
+		    raise service_error(service_error.internal,
+			    "Release project subprocess creation error "+ \
+				    "[%s] (%s)" %  (cmd[0], e.strerror))
+
+	    if rc != 0: 
+		raise service_error(service_error.internal,
+			"Release project subprocess error " +\
+				"[%s] (%d)" % (cmd[0], rc))
+
+	return { 'project': req['ReleaseProjectRequestBody']['project']}
+
+class allocate_project_remote:
+    """
+    Allocate projects on a remote machine using the internal SOAP interface
+    """
+    class proxy(service_caller):
+	"""
+	This class is a proxy functor (callable) that has the same signature as
+	a function called by soap_handler or xmlrpc_handler, but that used the
+	service_caller class to call the function remotely.
+	"""
+
+	def __init__(self, url, cert_file, cert_pwd, trusted_certs, auth, 
+		method):
+	    service_caller.__init__(self, method)
+	    self.url = url
+	    self.cert_file = cert_file
+	    self.cert_pwd = cert_pwd
+	    self.trusted_certs = trusted_certs
+	    self.request_body__name = "%sRequestBody" % method
+	    self.resp_name = "%sResponseBody" % method
+	    self.auth = auth
+	    # Calling the proxy object directly invokes the proxy_call method,
+	    # not the service_call method.
+	    self.__call__ = self.proxy_call
+	    
+
+	# Define the proxy, NB, the parameters to make_proxy are visible to the
+	# definition of proxy.
+	def proxy_call(self, req, fid=None):
+	    """
+	    Send req on to a remote project instantiator.
+
+	    Req is just the message to be sent.  This function re-wraps it.
+	    It also rethrows any faults.
+	    """
+
+	    if req.has_key(self.request_body_name):
+		req = req[self.request_body_name]
+	    else:
+		print "request error"
+		raise service_error(service_error.req, "Bad formated request");
+
+	    r = self.call_service(self.url, req, self.cert_file, self.cert_pwd,
+		    self.trusted_certs)
+	    if r.has_key(self.resp_name):
+		return r[self.resp_name]
+	    else:
+		print "response error"
+		raise service_error(service_error.protocol, 
+			"Bad proxy response")
+
+    # back to defining the allocate_project_remote class
+    def __init__(self, config, auth=None):
+	"""
+	Initializer.  Parses a configuration if one is given.
+	"""
+
+	self.debug = config.get("allocate", "debug", False)
+	self.url = config.get("allocate", "uri", "")
+
+	self.cert_file = config.get("allocate", "cert_file", None)
+	self.cert_pwd = config.get("allocate", "cert_pwd", None)
+	self.trusted_certs = config.get("allocate", "trusted_certs", None)
+
+	# Certs are promoted from the generic to the specific, so without a if
+	# no dynamic project certificates, then proxy certs are used, and if
+	# none of those the main certs.
+
+	if config.has_option("globals", "proxy_cert_file"):
+	    if not self.cert_file:
+		self.cert_file = config.get("globals", "proxy_cert_file")
+		if config.has_option("globals", "proxy_cert_pwd"):
+		    self.cert_pwd = config.get("globals", "proxy_cert_pwd")
+
+	if config.has_option("globals", "proxy_trusted_certs") and \
+		not self.trusted_certs:
+		self.trusted_certs = \
+			config.get("globals", "proxy_trusted_certs")
+
+	if config.has_option("globals", "cert_file"):
+	    has_pwd = config.has_option("globals", "cert_pwd")
+	    if not self.cert_file:
+		self.cert_file = config.get("globals", "cert_file")
+		if has_pwd: 
+		    self.cert_pwd = config.get("globals", "cert_pwd")
+
+	if config.get("globals", "trusted_certs") and not self.trusted_certs:
+		self.trusted_certs = \
+			config.get("globals", "trusted_certs")
+
+	self.soap_services = { }
+	self.xmlrpc_services = { }
+	self.log = logging.getLogger("fedd.allocate.remote")
+	set_log_level(config, "allocate", self.log)
+
+	if auth:
+	    self.auth = auth
+	else:
+	    auth = authorizer()
+	    log.warn("[allocate] No authorizer passed in, using local one")
+
+	# The specializations of the proxy functions
+	self.dynamic_project = self.proxy(self.url, self.cert_file, 
+		self.cert_pwd, self.trusted_certs, self.auth, 
+		"AllocateProject")
+	self.static_project = self.proxy(self.url, self.cert_file, 
+		self.cert_pwd, self.trusted_certs, self.auth, 
+		"StaticProject")
+	self.release_project = self.proxy(self.url, self.cert_file,
+		self.cert_pwd, self.trusted_certs, self.auth, 
+		"ReleaseProject")
+
Index: fedd/federation/asn1_raw.py
===================================================================
--- fedd/federation/asn1_raw.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/asn1_raw.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,160 @@
+# Modified from an example in the Python ASN.1 package to read certificate
+# files and extract key bits.  More recent versions of m2crypto support this
+# more directly.
+
+import sys, string, base64
+from pyasn1.type import tag,namedtype,namedval,univ,constraint,char,useful
+from pyasn1.codec.der import decoder, encoder
+from pyasn1 import error
+
+from M2Crypto import EVP
+
+# Would be autogenerated from ASN.1 source by a ASN.1 parser
+# X.509 spec (rfc2459)
+
+MAX = 64  # XXX ?
+
+class DirectoryString(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
+        namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
+        namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
+        namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
+        namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
+        namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
+        )
+
+class AttributeValue(DirectoryString): pass
+
+class AttributeType(univ.ObjectIdentifier): pass
+
+class AttributeTypeAndValue(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('type', AttributeType()),
+        namedtype.NamedType('value', AttributeValue())
+        )
+
+class RelativeDistinguishedName(univ.SetOf):
+    componentType = AttributeTypeAndValue()
+
+class RDNSequence(univ.SequenceOf):
+    componentType = RelativeDistinguishedName()
+
+class Name(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('', RDNSequence())
+        )
+                          
+class AlgorithmIdentifier(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
+        namedtype.OptionalNamedType('parameters', univ.Null())
+        # XXX syntax screwed?
+#        namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
+        )
+
+class Extension(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('extnID', univ.ObjectIdentifier()),
+        namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
+        namedtype.NamedType('extnValue', univ.OctetString())
+        )
+
+class Extensions(univ.SequenceOf):
+    componentType = Extension()
+    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
+
+class SubjectPublicKeyInfo(univ.Sequence):
+     componentType = namedtype.NamedTypes(
+         namedtype.NamedType('algorithm', AlgorithmIdentifier()),
+         namedtype.NamedType('subjectPublicKey', univ.BitString())
+         )
+
+class UniqueIdentifier(univ.BitString): pass
+
+class Time(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('utcTime', useful.UTCTime()),
+        namedtype.NamedType('generalTime', useful.GeneralizedTime())
+        )
+    
+class Validity(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('notBefore', Time()),
+        namedtype.NamedType('notAfter', Time())
+        )
+
+class CertificateSerialNumber(univ.Integer): pass
+
+class Version(univ.Integer):
+    namedValues = namedval.NamedValues(
+        ('v1', 0), ('v2', 1), ('v3', 2)
+        )
+
+class TBSCertificate(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
+        namedtype.NamedType('serialNumber', CertificateSerialNumber()),
+        namedtype.NamedType('signature', AlgorithmIdentifier()),
+        namedtype.NamedType('issuer', Name()),
+        namedtype.NamedType('validity', Validity()),
+        namedtype.NamedType('subject', Name()),
+        namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
+        namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
+        namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
+        namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
+        )
+
+class Certificate(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('tbsCertificate', TBSCertificate()),
+        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
+        namedtype.NamedType('signatureValue', univ.BitString())
+        )
+
+# end of ASN.1 data structures
+
+
+def get_bits(substrate):
+    certType = Certificate()
+    cert = decoder.decode(substrate, asn1Spec=certType)[0]
+    n = cert
+    for name in ('tbsCertificate', 'subjectPublicKeyInfo', 
+	    'subjectPublicKey'):
+	n = n.getComponentByName(name)
+	if n == None: raise RuntimeException("Bad cert")
+
+    b = []
+    for i in range(0, len(n), 8):
+	v = 0
+	for j in range(0, 8):
+	    v = (v << 1) + n[i+j]
+	b.append(v)
+
+    return str().join([chr(x) for x in b])
+
+
+def get_key_bits_from_file(fname):
+    stSpam, stHam, stDump = 0, 1, 2
+    state = stSpam
+
+    f = open(fname)
+
+    for certLine in f:
+	certLine = string.strip(certLine)
+	if state == stSpam:
+	    if certLine == '-----BEGIN CERTIFICATE-----':
+		substrate = ""
+		state = stHam
+		continue
+	if state == stHam:
+	    if certLine == '-----END CERTIFICATE-----':
+		state = stDump
+	    else:
+		substrate = substrate + base64.b64decode(certLine)
+	if state == stDump:
+	    f.close()
+	    return get_bits(substrate)
+
+def get_key_bits_from_cert(cert):
+    return get_bits(cert.as_der())
Index: fedd/federation/authorizer.py
===================================================================
--- fedd/federation/authorizer.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/authorizer.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,125 @@
+#/usr/local/bin/python
+
+from fedid import fedid
+
+class authorizer:
+    """
+    This class keeps track of authorization attributes for the various modules
+    running.  When it gets smarter it will be the basis for a real
+    attribute-based authentication system.
+    """
+    # general error exception for badly formed names.  
+    class bad_name(RuntimeError): pass
+
+    def __init__(self, def_attr="testbed"):
+	self.attrs = { }
+	self.globals=set()
+
+    @staticmethod
+    def auth_name(name):
+	"""
+	Helper to convert a non-unicode local name to a unicode string.  Mixed
+	representations can needlessly confuse the authorizer.
+	"""
+	if isinstance(name, basestring):
+	    if not isinstance(name, unicode): return unicode(name)
+	    else: return name
+	else: return name
+
+    def valid_name(self, name):
+	"""
+	Ensure that the given name is valid.  A valid name can either be a
+	triple of strings and fedids representing one of our generalized Emulab
+	names or a single fedid.  Compound names can include wildcards (None)
+	and must anchor to a fedid at their highest level (unless they're all
+	None)
+
+	This either returns True or throws an exception.  More an assertion
+	than a function.
+	"""
+	if isinstance(name, tuple) and len(name) == 3:
+	    for n in name:
+		if n: 
+		    if not (isinstance(n, basestring) or isinstance(n, fedid)):
+			raise self.bad_name("names must be either a " +\
+				"triple or a fedid")
+	    for n in name: 
+		if n:
+		    if isinstance(n, fedid):
+			return True
+		    else:
+			raise self.bad_name("Compound names must be " + \
+				"rooted in fedids: %s" % str(name))
+
+	    return True
+	elif isinstance(name, fedid):
+	    return True
+	else:
+	    raise self.bad_name("Names must be a triple or a fedid (%s)" % name)
+
+    def set_attribute(self, name, attr):
+	"""
+	Attach attr to name.  Multiple attrs can be attached.
+	"""
+	self.valid_name(name)
+	if isinstance(name, tuple):
+	    aname = tuple([ self.auth_name(n) for n in name])
+	else:
+	    aname = self.auth_name(name)
+
+	if not self.attrs.has_key(aname):
+	    self.attrs[aname] = set()
+	self.attrs[aname].add(attr)
+
+    def unset_attribute(self, name, attr):
+	"""
+	Remove an attribute from name
+	"""
+	self.valid_name(name)
+	if isinstance(name, tuple):
+	    aname = tuple([ self.auth_name(n) for n in name])
+	else:
+	    aname = self.auth_name(name)
+
+	attrs = self.attrs.get(aname, None)
+	if attrs: attrs.discard(attr)
+
+    def check_attribute(self, name, attr):
+	"""
+	Return True if name has attr (or if attr is global).  Tuple names match
+	any tuple name that matches all names present and has None entries in
+	other fileds.  For tuple names True implies that there is a matching
+	tuple name with the attribute.
+	"""
+	def tup(tup, i, p):
+	    mask = 1 << i
+	    if p & mask : return authorizer.auth_name(tup[i])
+	    else: return None
+
+	self.valid_name(name)
+	if attr in self.globals:
+	    return True
+
+	if isinstance(name, tuple):
+	    for p in range(0,8):
+		lookup = ( tup(name, 0, p), tup(name,1, p), tup(name,2,p))
+		if self.attrs.has_key(lookup):
+		    if attr in self.attrs[lookup]:
+			return True
+	else:
+	    return  attr in self.attrs.get(self.auth_name(name), set())
+
+    def set_global_attribute(self, attr):
+	"""
+	Set a global attribute.  All names, even those otherwise unknown to the
+	authorizer have this attribute.
+	"""
+	self.globals.add(attr)
+
+    def unset_global_attribute(self, attr):
+	"""
+	Remove a global attribute
+	"""
+
+	self.globals.discard(attr)
+
Index: fedd/federation/config_parser.py
===================================================================
--- fedd/federation/config_parser.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/config_parser.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,45 @@
+#!/usr/local/bin/python
+
+from ConfigParser import *
+
+class config_parser(SafeConfigParser):
+    """
+    A SafeConfig parser with a more forgiving get attribute
+    """
+
+    def safe_get(self, sect, opt, method, default=None):
+	"""
+	If the option is present, return it, otherwise the default.
+	"""
+	if self.has_option(sect, opt): return method(self, sect, opt)
+	else: return default
+
+    def get(self, sect, opt, default=None):
+	"""
+	This returns the requested option or a given default.
+
+	It's more like getattr than get.
+	"""
+
+	return self.safe_get(sect, opt, SafeConfigParser.get, default)
+
+    def getint(self, sect, opt, default=0):
+	"""
+	Returns the selected option as an int or a default.
+	"""
+
+	return self.safe_get(sect, opt, SafeConfigParser.getint, default)
+
+    def getfloat(self, sect, opt, default=0.0):
+	"""
+	Returns the selected option as an int or a default.
+	"""
+
+	return self.safe_get(sect, opt, SafeConfigParser.getfloat, default)
+
+    def getboolean(self, sect, opt, default=False):
+	"""
+	Returns the selected option as a boolean or a default.
+	"""
+
+	return self.safe_get(sect, opt, SafeConfigParser.getboolean, default)
Index: fedd/federation/deter_impl.py
===================================================================
--- fedd/federation/deter_impl.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/deter_impl.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,63 @@
+#!/usr/local/bin/python
+
+from access import access
+from experiment_control import experiment_control_local
+from split import split_local
+from util import read_simple_accessdb
+
+from authorizer import authorizer
+
+class deter_impl:
+    """
+    The implementation of access control based on mapping users to projects.
+
+    Users can be mapped to existing projects or have projects created
+    dynamically.  This implements both direct requests and proxies.
+    """
+    # Used by the SOAP caller
+    soap_namespaces = ('http://www.isi.edu/faber/fedd.wsdl',
+	    'http://www.isi.edu/faber/fedd_internal.wsdl')
+
+    def __init__(self, config=None):
+	"""
+	Initializer.  Uses the parsed configuration to create appropriate
+	components.
+	"""
+	self.soap_services = { }
+	self.xmlrpc_services = { }
+	self.auth = authorizer()
+
+	if config:
+	    self.cert_file = config.get("globals", "cert_file");
+	    self.cert_pwd = config.get("globals", "cert_pwd");
+	    self.trusted_certs = config.get("globals", "trusted_certs");
+
+	    access_db = config.get("globals", "accessdb")
+
+	    if access_db:
+		try:
+		    read_simple_accessdb(access_db, self.auth)
+		except IOError, e:
+		    raise service_error(service_error.internal,
+			    "Error reading accessDB %s: %s" % (access_db, e))
+		except ValueError:
+		    raise service_error(service_error.internal, "%s" % e)
+
+	    if config.has_section("access"):
+		self.access = access(config, self.auth)
+		self.soap_services.update(self.access.soap_services) 
+		self.xmlrpc_services.update(self.access.xmlrpc_services) 
+
+	    if config.has_section("experiment_control"):
+		self.experiment = \
+			experiment_control_local(config, self.auth)
+		self.soap_services.update(self.experiment.soap_services) 
+		self.xmlrpc_services.update(self.experiment.xmlrpc_services) 
+
+	    if config.has_section("splitter"):
+		self.splitter = split_local(config, self.auth)
+		self.soap_services.update(self.splitter.soap_services) 
+		self.xmlrpc_services.update(self.splitter.xmlrpc_services) 
+
+def new_feddservice(config):
+    return deter_impl(config)
Index: fedd/federation/experiment_control.py
===================================================================
--- fedd/federation/experiment_control.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/experiment_control.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,2005 @@
+#!/usr/local/bin/python
+
+import os,sys
+
+import re
+import random
+import string
+import subprocess
+import tempfile
+import copy
+import pickle
+import logging
+
+import traceback
+# For parsing visualization output and splitter output
+import xml.parsers.expat
+
+from threading import *
+from subprocess import *
+
+from util import *
+from fedid import fedid, generate_fedid
+from remote_service import xmlrpc_handler, soap_handler, service_caller
+from service_error import service_error
+
+
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.experiment_control")
+fl.addHandler(nullHandler())
+
+class experiment_control_local:
+    """
+    Control of experiments that this system can directly access.
+
+    Includes experiment creation, termination and information dissemination.
+    Thred safe.
+    """
+    
+    class thread_pool:
+	"""
+	A class to keep track of a set of threads all invoked for the same
+	task.  Manages the mutual exclusion of the states.
+	"""
+	def __init__(self):
+	    """
+	    Start a pool.
+	    """
+	    self.changed = Condition()
+	    self.started = 0
+	    self.terminated = 0
+
+	def acquire(self):
+	    """
+	    Get the pool's lock.
+	    """
+	    self.changed.acquire()
+
+	def release(self):
+	    """
+	    Release the pool's lock.
+	    """
+	    self.changed.release()
+
+	def wait(self, timeout = None):
+	    """
+	    Wait for a pool thread to start or stop.
+	    """
+	    self.changed.wait(timeout)
+
+	def start(self):
+	    """
+	    Called by a pool thread to report starting.
+	    """
+	    self.changed.acquire()
+	    self.started += 1
+	    self.changed.notifyAll()
+	    self.changed.release()
+
+	def terminate(self):
+	    """
+	    Called by a pool thread to report finishing.
+	    """
+	    self.changed.acquire()
+	    self.terminated += 1
+	    self.changed.notifyAll()
+	    self.changed.release()
+
+	def clear(self):
+	    """
+	    Clear all pool data.
+	    """
+	    self.changed.acquire()
+	    self.started = 0
+	    self.terminated =0
+	    self.changed.notifyAll()
+	    self.changed.release()
+
+    class pooled_thread(Thread):
+	"""
+	One of a set of threads dedicated to a specific task.  Uses the
+	thread_pool class above for coordination.
+	"""
+	def __init__(self, group=None, target=None, name=None, args=(), 
+		kwargs={}, pdata=None, trace_file=None):
+	    Thread.__init__(self, group, target, name, args, kwargs)
+	    self.rv = None	    # Return value of the ops in this thread
+	    self.exception = None   # Exception that terminated this thread
+	    self.target=target	    # Target function to run on start()
+	    self.args = args	    # Args to pass to target
+	    self.kwargs = kwargs    # Additional kw args
+	    self.pdata = pdata	    # thread_pool for this class
+	    # Logger for this thread
+	    self.log = logging.getLogger("fedd.experiment_control")
+	
+	def run(self):
+	    """
+	    Emulate Thread.run, except add pool data manipulation and error
+	    logging.
+	    """
+	    if self.pdata:
+		self.pdata.start()
+
+	    if self.target:
+		try:
+		    self.rv = self.target(*self.args, **self.kwargs)
+		except service_error, s:
+		    self.exception = s
+		    self.log.error("Thread exception: %s %s" % \
+			    (s.code_string(), s.desc))
+		except:
+		    self.exception = sys.exc_info()[1]
+		    self.log.error(("Unexpected thread exception: %s" +\
+			    "Trace %s") % (self.exception,\
+				traceback.format_exc()))
+	    if self.pdata:
+		self.pdata.terminate()
+
+    call_RequestAccess = service_caller('RequestAccess')
+    call_ReleaseAccess = service_caller('ReleaseAccess')
+    call_Ns2Split = service_caller('Ns2Split')
+
+    def __init__(self, config=None, auth=None):
+	"""
+	Intialize the various attributes, most from the config object
+	"""
+	self.thread_with_rv = experiment_control_local.pooled_thread
+	self.thread_pool = experiment_control_local.thread_pool
+
+	self.cert_file = None
+	self.cert_pwd = None
+	self.trusted_certs = None
+
+	# Walk through the various relevant certificat specifying config
+	# attributes until the local certificate attributes can be resolved.
+	# The walk is from most specific to most general specification.
+	for s in ("experiment_control", "globals"):
+	    if config.has_section(s): 
+		if config.has_option(s, "cert_file"):
+		    if not self.cert_file:
+			self.cert_file = config.get(s, "cert_file")
+			self.cert_pwd = config.get(s, "cert_pwd")
+
+		if config.has_option(s, "trusted_certs"):
+		    if not self.trusted_certs:
+			self.trusted_certs = config.get(s, "trusted_certs")
+
+
+	self.exp_stem = "fed-stem"
+	self.log = logging.getLogger("fedd.experiment_control")
+	set_log_level(config, "experiment_control", self.log)
+	self.muxmax = 2
+	self.nthreads = 2
+	self.randomize_experiments = False
+
+	self.scp_exec = "/usr/bin/scp"
+	self.splitter = None
+	self.ssh_exec="/usr/bin/ssh"
+	self.ssh_keygen = "/usr/bin/ssh-keygen"
+	self.ssh_identity_file = None
+
+
+	self.debug = config.getboolean("experiment_control", "create_debug")
+	self.state_filename = config.get("experiment_control", 
+		"experiment_state_file")
+	self.splitter_url = config.get("experiment_control", "splitter_url")
+	self.fedkit = config.get("experiment_control", "fedkit")
+	accessdb_file = config.get("experiment_control", "accessdb")
+
+	self.ssh_pubkey_file = config.get("experiment_control", 
+		"ssh_pubkey_file")
+	self.ssh_privkey_file = config.get("experiment_control",
+		"ssh_privkey_file")
+	# NB for internal master/slave ops, not experiment setup
+	self.ssh_type = config.get("experiment_control", "sshkeytype", "rsa")
+	self.state = { }
+	self.state_lock = Lock()
+	self.tclsh = "/usr/local/bin/otclsh"
+	self.tcl_splitter = "/usr/testbed/lib/ns2ir/parse.tcl"
+	mapdb_file = config.get("experiment_control", "mapdb")
+	self.trace_file = sys.stderr
+
+	self.def_expstart = \
+		"sudo -H /bin/sh /usr/local/federation/bin/federate.sh >& " +\
+		"/tmp/federate";
+	self.def_mexpstart = "sudo -H /usr/local/federation/bin/make_hosts " +\
+		"FEDDIR/hosts";
+	self.def_gwstart = \
+		"sudo -H /usr/local/federation/bin/fed-tun.pl -f GWCONF>& " +\
+		"/tmp/bridge.log";
+	self.def_mgwstart = \
+		"sudo -H /usr/local/federation/bin/fed-tun.pl -f GWCONF >& " +\
+		"/tmp/bridge.log";
+	self.def_gwimage = "FBSD61-TUNNEL2";
+	self.def_gwtype = "pc";
+
+	if auth:
+	    self.auth = auth
+	else:
+	    self.log.error(\
+		    "[access]: No authorizer initialized, creating local one.")
+	    auth = authorizer()
+
+
+	if self.ssh_pubkey_file:
+	    try:
+		f = open(self.ssh_pubkey_file, 'r')
+		self.ssh_pubkey = f.read()
+		f.close()
+	    except IOError:
+		raise service_error(service_error.internal,
+			"Cannot read sshpubkey")
+	else:
+	    raise service_error(service_error.internal, 
+		    "No SSH public key file?")
+
+	if not self.ssh_privkey_file:
+	    raise service_error(service_error.internal, 
+		    "No SSH public key file?")
+
+
+	if mapdb_file:
+	    self.read_mapdb(mapdb_file)
+	else:
+	    self.log.warn("[experiment_control] No testbed map, using defaults")
+	    self.tbmap = { 
+		    'deter':'https://users.isi.deterlab.net:23235',
+		    'emulab':'https://users.isi.deterlab.net:23236',
+		    'ucb':'https://users.isi.deterlab.net:23237',
+		    }
+
+	if accessdb_file:
+		self.read_accessdb(accessdb_file)
+	else:
+	    raise service_error(service_error.internal,
+		    "No accessdb specified in config")
+
+	# Grab saved state.  OK to do this w/o locking because it's read only
+	# and only one thread should be in existence that can see self.state at
+	# this point.
+	if self.state_filename:
+	    self.read_state()
+
+	# Dispatch tables
+	self.soap_services = {\
+		'Create': soap_handler('Create', self.create_experiment),
+		'Vtopo': soap_handler('Vtopo', self.get_vtopo),
+		'Vis': soap_handler('Vis', self.get_vis),
+		'Info': soap_handler('Info', self.get_info),
+		'Terminate': soap_handler('Terminate', 
+		    self.terminate_experiment),
+	}
+
+	self.xmlrpc_services = {\
+		'Create': xmlrpc_handler('Create', self.create_experiment),
+		'Vtopo': xmlrpc_handler('Vtopo', self.get_vtopo),
+		'Vis': xmlrpc_handler('Vis', self.get_vis),
+		'Info': xmlrpc_handler('Info', self.get_info),
+		'Terminate': xmlrpc_handler('Terminate',
+		    self.terminate_experiment),
+	}
+
+    def copy_file(self, src, dest, size=1024):
+	"""
+	Exceedingly simple file copy.
+	"""
+	s = open(src,'r')
+	d = open(dest, 'w')
+
+	buf = "x"
+	while buf != "":
+	    buf = s.read(size)
+	    d.write(buf)
+	s.close()
+	d.close()
+
+    # Call while holding self.state_lock
+    def write_state(self):
+	"""
+	Write a new copy of experiment state after copying the existing state
+	to a backup.
+
+	State format is a simple pickling of the state dictionary.
+	"""
+	if os.access(self.state_filename, os.W_OK):
+	    self.copy_file(self.state_filename, \
+		    "%s.bak" % self.state_filename)
+	try:
+	    f = open(self.state_filename, 'w')
+	    pickle.dump(self.state, f)
+	except IOError, e:
+	    self.log.error("Can't write file %s: %s" % \
+		    (self.state_filename, e))
+	except pickle.PicklingError, e:
+	    self.log.error("Pickling problem: %s" % e)
+	except TypeError, e:
+	    self.log.error("Pickling problem (TypeError): %s" % e)
+
+    # Call while holding self.state_lock
+    def read_state(self):
+	"""
+	Read a new copy of experiment state.  Old state is overwritten.
+
+	State format is a simple pickling of the state dictionary.
+	"""
+	try:
+	    f = open(self.state_filename, "r")
+	    self.state = pickle.load(f)
+	    self.log.debug("[read_state]: Read state from %s" % \
+		    self.state_filename)
+	except IOError, e:
+	    self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
+		    % (self.state_filename, e))
+	except pickle.UnpicklingError, e:
+	    self.log.warning(("[read_state]: No saved state: " + \
+		    "Unpickling failed: %s") % e)
+	
+	for k in self.state.keys():
+	    try:
+		# This list should only have one element in it, but phrasing it
+		# as a for loop doesn't cost much, really.  We have to find the
+		# fedid elements anyway.
+		for eid in [ f['fedid'] \
+			for f in self.state[k]['experimentID']\
+			    if f.has_key('fedid') ]:
+		    self.auth.set_attribute(self.state[k]['owner'], eid)
+	    except KeyError, e:
+		self.log.warning("[read_state]: State ownership or identity " +\
+			"misformatted in %s: %s" % (self.state_filename, e))
+
+
+    def read_accessdb(self, accessdb_file):
+	"""
+	Read the mapping from fedids that can create experiments to their name
+	in the 3-level access namespace.  All will be asserted from this
+	testbed and can include the local username and porject that will be
+	asserted on their behalf by this fedd.  Each fedid is also added to the
+	authorization system with the "create" attribute.
+	"""
+	self.accessdb = {}
+	# These are the regexps for parsing the db
+	name_expr = "[" + string.ascii_letters + string.digits + "\.\-]+"
+	project_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
+		"\s*->\(\s*("+name_expr+")\s*,\s*("+name_expr+")\s*\)\s*$")
+	user_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
+		"\s*->\s*(" + name_expr + ")\s*$")
+	lineno = 0
+
+	# Parse the mappings and store in self.authdb, a dict of 
+	# fedid -> (proj, user)
+	try:
+	    f = open(accessdb_file, "r")
+	    for line in f:
+		lineno += 1
+		line = line.strip()
+		if len(line) == 0 or line.startswith('#'):
+		    continue
+		m = project_line.match(line)
+		if m:
+		    fid = fedid(hexstr=m.group(1))
+		    project, user = m.group(2,3)
+		    if not self.accessdb.has_key(fid):
+			self.accessdb[fid] = []
+		    self.accessdb[fid].append((project, user))
+		    continue
+
+		m = user_line.match(line)
+		if m:
+		    fid = fedid(hexstr=m.group(1))
+		    project = None
+		    user = m.group(2)
+		    if not self.accessdb.has_key(fid):
+			self.accessdb[fid] = []
+		    self.accessdb[fid].append((project, user))
+		    continue
+		self.log.warn("[experiment_control] Error parsing access " +\
+			"db %s at line %d" %  (accessdb_file, lineno))
+	except IOError:
+	    raise service_error(service_error.internal,
+		    "Error opening/reading %s as experiment " +\
+			    "control accessdb" %  accessdb_file)
+	f.close()
+
+	# Initialize the authorization attributes
+	for fid in self.accessdb.keys():
+	    self.auth.set_attribute(fid, 'create')
+
+    def read_mapdb(self, file):
+	"""
+	Read a simple colon separated list of mappings for the
+	label-to-testbed-URL mappings.  Clears or creates self.tbmap.
+	"""
+
+	self.tbmap = { }
+	lineno =0
+	try:
+	    f = open(file, "r")
+	    for line in f:
+		lineno += 1
+		line = line.strip()
+		if line.startswith('#') or len(line) == 0:
+		    continue
+		try:
+		    label, url = line.split(':', 1)
+		    self.tbmap[label] = url
+		except ValueError, e:
+		    self.log.warn("[read_mapdb] Ignored bad line (%d) in " +\
+			    "map db: %s %s" % (lineno, line, e))
+	except IOError, e:
+	    self.log.warning("[read_mapdb]: No saved map database: Can't " +\
+		    "open %s: %s" % (file, e))
+	f.close()
+
+    def scp_file(self, file, user, host, dest=""):
+	"""
+	scp a file to the remote host.  If debug is set the action is only
+	logged.
+	"""
+
+	scp_cmd = [self.scp_exec, '-i', self.ssh_privkey_file, file, 
+		"%s@%s:%s" % (user, host, dest)]
+	rv = 0
+
+	try:
+	    dnull = open("/dev/null", "r")
+	except IOError:
+	    self.log.debug("[ssh_file]: failed to open /dev/null for redirect")
+	    dnull = Null
+
+	self.log.debug("[scp_file]: %s" % " ".join(scp_cmd))
+	if not self.debug:
+	    if dnull: rv = call(scp_cmd, stdout=dnull, stderr=dnull)
+	    else: rv = call(scp_cmd)
+
+	return rv == 0
+
+    def ssh_cmd(self, user, host, cmd, wname=None):
+	"""
+	Run a remote command on host as user.  If debug is set, the action is
+	only logged.
+	"""
+	sh_str = "%s -i %s %s@%s %s" % (self.ssh_exec, self.ssh_privkey_file, 
+		user, host, cmd)
+
+	try:
+	    dnull = open("/dev/null", "r")
+	except IOError:
+	    self.log.debug("[ssh_cmd]: failed to open /dev/null for redirect")
+	    dnull = Null
+
+	self.log.debug("[ssh_cmd]: %s" % sh_str)
+	if not self.debug:
+	    if dnull:
+		sub = Popen(sh_str, shell=True, stdout=dnull, stderr=dnull)
+	    else:
+		sub = Popen(sh_str, shell=True)
+	    return sub.wait() == 0
+	else:
+	    return True
+
+    def ship_configs(self, host, user, src_dir, dest_dir):
+	"""
+	Copy federant-specific configuration files to the federant.
+	"""
+	if not self.ssh_cmd(user, host, "mkdir -p %s" % dest_dir):
+	    return False
+	if not self.ssh_cmd(user, host, "chmod 770 %s" % dest_dir):
+	    return False
+
+	for f in os.listdir(src_dir):
+	    if os.path.isdir(f):
+		if not self.ship_configs(host, user, "%s/%s" % (src_dir, f), 
+			"%s/%s" % (dest_dir, f)):
+		    return False
+	    else:
+		if not self.scp_file("%s/%s" % (src_dir, f), 
+			user, host, dest_dir):
+		    return False
+	return True
+
+    def start_segment(self, tb, eid, tbparams, tmpdir, timeout=0):
+	"""
+	Start a sub-experiment on a federant.
+
+	Get the current state, modify or create as appropriate, ship data and
+	configs and start the experiment.  There are small ordering differences
+	based on the initial state of the sub-experiment.
+	"""
+	# ops node in the federant
+	host = "%s%s" % (tbparams[tb]['host'], tbparams[tb]['domain'])
+	user = tbparams[tb]['user']	# federant user
+	pid = tbparams[tb]['project']	# federant project
+	# XXX
+	base_confs = ( "hosts",)
+	tclfile = "%s.%s.tcl" % (eid, tb)   # sub-experiment description
+	# command to test experiment state
+	expinfo_exec = "/usr/testbed/bin/expinfo"  
+	# Configuration directories on the remote machine
+	proj_dir = "/proj/%s/exp/%s/tmp" % (pid, eid)
+	tarfiles_dir = "/proj/%s/tarfiles/%s" % (pid, eid)
+	rpms_dir = "/proj/%s/rpms/%s" % (pid, eid)
+	# Regular expressions to parse the expinfo response
+	state_re = re.compile("State:\s+(\w+)")
+	no_exp_re = re.compile("^No\s+such\s+experiment")
+	state = None	# Experiment state parsed from expinfo
+	# The expinfo ssh command
+	cmd = [self.ssh_exec, "%s@%s" % (user, host), expinfo_exec, pid, eid]
+
+	# Get status
+	self.log.debug("[start_segment]: %s"% " ".join(cmd))
+	dev_null = None
+	try:
+	    dev_null = open("/dev/null", "a")
+	except IOError, e:
+	    self.log.error("[start_segment]: can't open /dev/null: %s" %e)
+
+	if self.debug:
+	    state = 'swapped'
+	    rv = 0
+	else:
+	    status = Popen(cmd, stdout=PIPE, stderr=dev_null)
+	    for line in status.stdout:
+		m = state_re.match(line)
+		if m: state = m.group(1)
+		else:
+		    m = no_exp_re.match(line)
+		    if m: state = "none"
+	    rv = status.wait()
+
+	# If the experiment is not present the subcommand returns a non-zero
+	# return value.  If we successfully parsed a "none" outcome, ignore the
+	# return code.
+	if rv != 0 and state != "none":
+	    raise service_error(service_error.internal,
+		    "Cannot get status of segment %s:%s/%s" % (tb, pid, eid))
+
+	self.log.debug("[start_segment]: %s: %s" % (tb, state))
+	self.log.info("[start_segment]:transferring experiment to %s" % tb)
+
+	if not self.scp_file("%s/%s/%s" % (tmpdir, tb, tclfile), user, host):
+	    return False
+	# Clear the federation files
+	if not self.ssh_cmd(user, host, 
+		"/bin/sh -c \"'/bin/rm -rf %s'\"" % proj_dir):
+	    return False
+	if not self.ssh_cmd(user, host, 
+		"/bin/sh -c \"'mkdir -p %s'\"" % proj_dir):
+	    return False
+	# Clear and create the tarfiles and rpm directories
+	for d in (tarfiles_dir, rpms_dir):
+	    if not self.ssh_cmd(user, host, 
+		    "/bin/sh -c \"'/bin/rm -rf %s/*'\"" % d):
+		return False
+	    if not self.ssh_cmd(user, host, "mkdir -p %s" % d, 
+		    "create tarfiles"):
+		return False
+	
+	if state == 'active':
+	    # Remote experiment is active.  Modify it.
+	    for f in base_confs:
+		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
+			"%s/%s" % (proj_dir, f)):
+		    return False
+	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
+		    proj_dir):
+		return False
+	    if os.path.isdir("%s/tarfiles" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/tarfiles" % tmpdir, tarfiles_dir):
+		    return False
+	    if os.path.isdir("%s/rpms" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/rpms" % tmpdir, tarfiles_dir):
+		    return False
+	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
+	    if not self.ssh_cmd(user, host,
+		    "/usr/testbed/bin/modexp -r -s -w %s %s %s" % \
+			    (pid, eid, tclfile), "modexp"):
+		return False
+	    return True
+	elif state == "swapped":
+	    # Remote experiment swapped out.  Modify it and swap it in.
+	    for f in base_confs:
+		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
+			"%s/%s" % (proj_dir, f)):
+		    return False
+	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
+		    proj_dir):
+		return False
+	    if os.path.isdir("%s/tarfiles" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/tarfiles" % tmpdir, tarfiles_dir):
+		    return False
+	    if os.path.isdir("%s/rpms" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/rpms" % tmpdir, tarfiles_dir):
+		    return False
+	    self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))
+	    if not self.ssh_cmd(user, host,
+		    "/usr/testbed/bin/modexp -w %s %s %s" % (pid, eid, tclfile),
+		    "modexp"):
+		return False
+	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
+	    if not self.ssh_cmd(user, host,
+		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
+		    "swapexp"):
+		return False
+	    return True
+	elif state == "none":
+	    # No remote experiment.  Create one.  We do this in 2 steps so we
+	    # can put the configuration files and scripts into the new
+	    # experiment directories.
+
+	    # Tarfiles must be present for creation to work
+	    if os.path.isdir("%s/tarfiles" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/tarfiles" % tmpdir, tarfiles_dir):
+		    return False
+	    if os.path.isdir("%s/rpms" % tmpdir):
+		if not self.ship_configs(host, user,
+			"%s/rpms" % tmpdir, tarfiles_dir):
+		    return False
+	    self.log.info("[start_segment]: Creating %s on %s" % (eid, tb))
+	    if not self.ssh_cmd(user, host,
+		    "/usr/testbed/bin/startexp -i -f -w -p %s -e %s %s" % \
+			    (pid, eid, tclfile), "startexp"):
+		return False
+	    # After startexp the per-experiment directories exist
+	    for f in base_confs:
+		if not self.scp_file("%s/%s" % (tmpdir, f), user, host,
+			"%s/%s" % (proj_dir, f)):
+		    return False
+	    if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb),
+		    proj_dir):
+		return False
+	    self.log.info("[start_segment]: Swapping %s in on %s" % (eid, tb))
+	    if not self.ssh_cmd(user, host,
+		    "/usr/testbed/bin/swapexp -w %s %s in" % (pid, eid),
+		    "swapexp"):
+		return False
+	    return True
+	else:
+	    self.log.debug("[start_segment]:unknown state %s" % state)
+	    return False
+
+    def stop_segment(self, tb, eid, tbparams):
+	"""
+	Stop a sub experiment by calling swapexp on the federant
+	"""
+	user = tbparams[tb]['user']
+	host = tbparams[tb]['host']
+	pid = tbparams[tb]['project']
+
+	self.log.info("[stop_segment]: Stopping %s on %s" % (eid, tb))
+	return self.ssh_cmd(user, host,
+		"/usr/testbed/bin/swapexp -w %s %s out" % (pid, eid))
+
+	
+    def generate_ssh_keys(self, dest, type="rsa" ):
+	"""
+	Generate a set of keys for the gateways to use to talk.
+
+	Keys are of type type and are stored in the required dest file.
+	"""
+	valid_types = ("rsa", "dsa")
+	t = type.lower();
+	if t not in valid_types: raise ValueError
+	cmd = [self.ssh_keygen, '-t', t, '-N', '', '-f', dest]
+
+	try:
+	    trace = open("/dev/null", "w")
+	except IOError:
+	    raise service_error(service_error.internal,
+		    "Cannot open /dev/null??");
+
+	# May raise CalledProcessError
+	self.log.debug("[generate_ssh_keys]: %s" % " ".join(cmd))
+	rv = call(cmd, stdout=trace, stderr=trace)
+	if rv != 0:
+	    raise service_error(service_error.internal, 
+		    "Cannot generate nonce ssh keys.  %s return code %d" \
+			    % (self.ssh_keygen, rv))
+
+    def gentopo(self, str):
+	"""
+	Generate the topology dtat structure from the splitter's XML
+	representation of it.
+
+	The topology XML looks like:
+	    <experiment>
+		<nodes>
+		    <node><vname></vname><ips>ip1:ip2</ips></node>
+		</nodes>
+		<lans>
+		    <lan>
+			<vname></vname><vnode></vnode><ip></ip>
+			<bandwidth></bandwidth><member>node:port</member>
+		    </lan>
+		</lans>
+	"""
+	class topo_parse:
+	    """
+	    Parse the topology XML and create the dats structure.
+	    """
+	    def __init__(self):
+		# Typing of the subelements for data conversion
+		self.str_subelements = ('vname', 'vnode', 'ips', 'ip', 'member')
+		self.int_subelements = ( 'bandwidth',)
+		self.float_subelements = ( 'delay',)
+		# The final data structure
+		self.nodes = [ ]
+		self.lans =  [ ]
+		self.topo = { \
+			'node': self.nodes,\
+			'lan' : self.lans,\
+		    }
+		self.element = { }  # Current element being created
+		self.chars = ""	    # Last text seen
+
+	    def end_element(self, name):
+		# After each sub element the contents is added to the current
+		# element or to the appropriate list.
+		if name == 'node':
+		    self.nodes.append(self.element)
+		    self.element = { }
+		elif name == 'lan':
+		    self.lans.append(self.element)
+		    self.element = { }
+		elif name in self.str_subelements:
+		    self.element[name] = self.chars
+		    self.chars = ""
+		elif name in self.int_subelements:
+		    self.element[name] = int(self.chars)
+		    self.chars = ""
+		elif name in self.float_subelements:
+		    self.element[name] = float(self.chars)
+		    self.chars = ""
+
+	    def found_chars(self, data):
+		self.chars += data.rstrip()
+
+
+	tp = topo_parse();
+	parser = xml.parsers.expat.ParserCreate()
+	parser.EndElementHandler = tp.end_element
+	parser.CharacterDataHandler = tp.found_chars
+
+	parser.Parse(str)
+
+	return tp.topo
+	
+
+    def genviz(self, topo):
+	"""
+	Generate the visualization the virtual topology
+	"""
+
+	neato = "/usr/local/bin/neato"
+	# These are used to parse neato output and to create the visualization
+	# file.
+	vis_re = re.compile('^\s*"?([\w\-]+)"?\s+\[.*pos="(\d+),(\d+)"')
+	vis_fmt = "<node><name>%s</name><x>%s</x><y>%s</y><type>" + \
+		"%s</type></node>"
+
+	try:
+	    # Node names
+	    nodes = [ n['vname'] for n in topo['node'] ]
+	    topo_lans = topo['lan']
+	except KeyError:
+	    raise service_error(service_error.internal, "Bad topology")
+
+	lans = { }
+	links = { }
+
+	# Walk through the virtual topology, organizing the connections into
+	# 2-node connections (links) and more-than-2-node connections (lans).
+	# When a lan is created, it's added to the list of nodes (there's a
+	# node in the visualization for the lan).
+	for l in topo_lans:
+	    if links.has_key(l['vname']):
+		if len(links[l['vname']]) < 2:
+		    links[l['vname']].append(l['vnode'])
+		else:
+		    nodes.append(l['vname'])
+		    lans[l['vname']] = links[l['vname']]
+		    del links[l['vname']]
+		    lans[l['vname']].append(l['vnode'])
+	    elif lans.has_key(l['vname']):
+		lans[l['vname']].append(l['vnode'])
+	    else:
+		links[l['vname']] = [ l['vnode'] ]
+
+
+	# Open up a temporary file for dot to turn into a visualization
+	try:
+	    df, dotname = tempfile.mkstemp()
+	    dotfile = os.fdopen(df, 'w')
+	except IOError:
+	    raise service_error(service_error.internal,
+		    "Failed to open file in genviz")
+
+	# Generate a dot/neato input file from the links, nodes and lans
+	try:
+	    print >>dotfile, "graph G {"
+	    for n in nodes:
+		print >>dotfile, '\t"%s"' % n
+	    for l in links.keys():
+		print >>dotfile, '\t"%s" -- "%s"' %  tuple(links[l])
+	    for l in lans.keys():
+		for n in lans[l]:
+		    print >>dotfile, '\t "%s" -- "%s"' % (n,l)
+	    print >>dotfile, "}"
+	    dotfile.close()
+	except TypeError:
+	    raise service_error(service_error.internal,
+		    "Single endpoint link in vtopo")
+	except IOError:
+	    raise service_error(service_error.internal, "Cannot write dot file")
+
+	# Use dot to create a visualization
+	dot = Popen([neato, '-Gstart=rand', '-Gepsilon=0.005', '-Gmaxiter=2000',
+		'-Gpack=true', dotname], stdout=PIPE)
+
+	# Translate dot to vis format
+	vis_nodes = [ ]
+	vis = { 'node': vis_nodes }
+	for line in dot.stdout:
+	    m = vis_re.match(line)
+	    if m:
+		vn = m.group(1)
+		vis_node = {'name': vn, \
+			'x': float(m.group(2)),\
+			'y' : float(m.group(3)),\
+		    }
+		if vn in links.keys() or vn in lans.keys():
+		    vis_node['type'] = 'lan'
+		else:
+		    vis_node['type'] = 'node'
+		vis_nodes.append(vis_node)
+	rv = dot.wait()
+
+	os.remove(dotname)
+	if rv == 0 : return vis
+	else: return None
+
+    def get_access(self, tb, nodes, user, tbparam, master, export_project,
+	    access_user):
+	"""
+	Get access to testbed through fedd and set the parameters for that tb
+	"""
+
+	translate_attr = {
+	    'slavenodestartcmd': 'expstart',
+	    'slaveconnectorstartcmd': 'gwstart',
+	    'masternodestartcmd': 'mexpstart',
+	    'masterconnectorstartcmd': 'mgwstart',
+	    'connectorimage': 'gwimage',
+	    'connectortype': 'gwtype',
+	    'tunnelcfg': 'tun',
+	    'smbshare': 'smbshare',
+	}
+
+	uri = self.tbmap.get(tb, None)
+	if not uri:
+	    raise service_error(serice_error.server_config, 
+		    "Unknown testbed: %s" % tb)
+
+	# currently this lumps all users into one service access group
+	service_keys = [ a for u in user \
+		for a in u.get('access', []) \
+		    if a.has_key('sshPubkey')]
+
+	if len(service_keys) == 0:
+	    raise service_error(service_error.req, 
+		    "Must have at least one SSH pubkey for services")
+
+
+	for p, u in access_user:
+	    self.log.debug(("[get_access] Attempting access from (%s, %s) " + \
+		    "to %s") %  ((p or "None"), u, uri))
+
+	    if p:
+		# Request with user and project specified
+		req = {\
+			'destinationTestbed' : { 'uri' : uri },
+			'project': { 
+			    'name': {'localname': p},
+			    'user': [ {'userID': { 'localname': u } } ],
+			    },
+			'user':  user,
+			'allocID' : { 'localname': 'test' },
+			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
+			'serviceAccess' : service_keys
+		    }
+	    else:
+		# Request with only user specified
+		req = {\
+			'destinationTestbed' : { 'uri' : uri },
+			'user':  [ {'userID': { 'localname': u } } ],
+			'allocID' : { 'localname': 'test' },
+			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
+			'serviceAccess' : service_keys
+		    }
+
+	    if tb == master:
+		# NB, the export_project parameter is a dict that includes
+		# the type
+		req['exportProject'] = export_project
+
+	    # node resources if any
+	    if nodes != None and len(nodes) > 0:
+		rnodes = [ ]
+		for n in nodes:
+		    rn = { }
+		    image, hw, count = n.split(":")
+		    if image: rn['image'] = [ image ]
+		    if hw: rn['hardware'] = [ hw ]
+		    if count: rn['count'] = int(count)
+		    rnodes.append(rn)
+		req['resources']= { }
+		req['resources']['node'] = rnodes
+
+	    try:
+		r = self.call_RequestAccess(uri, req, 
+			self.cert_file, self.cert_pwd, self.trusted_certs)
+	    except service_error, e:
+		if e.code == service_error.access:
+		    self.log.debug("[get_access] Access denied")
+		    r = None
+		    continue
+		else:
+		    raise e
+
+	    if r.has_key('RequestAccessResponseBody'):
+		# Through to here we have a valid response, not a fault.
+		# Access denied is a fault, so something better or worse than
+		# access denied has happened.
+		r = r['RequestAccessResponseBody']
+		self.log.debug("[get_access] Access granted")
+		break
+	    else:
+		raise service_error(service_error.protocol,
+			"Bad proxy response")
+	
+	if not r:
+	    raise service_error(service_error.access, 
+		    "Access denied by %s (%s)" % (tb, uri))
+
+	e = r['emulab']
+	p = e['project']
+	tbparam[tb] = { 
+		"boss": e['boss'],
+		"host": e['ops'],
+		"domain": e['domain'],
+		"fs": e['fileServer'],
+		"eventserver": e['eventServer'],
+		"project": unpack_id(p['name']),
+		"emulab" : e,
+		"allocID" : r['allocID'],
+		}
+	# Make the testbed name be the label the user applied
+	p['testbed'] = {'localname': tb }
+
+	for u in p['user']:
+	    tbparam[tb]['user'] = unpack_id(u['userID'])
+
+	for a in e['fedAttr']:
+	    if a['attribute']:
+		key = translate_attr.get(a['attribute'].lower(), None)
+		if key:
+		    tbparam[tb][key]= a['value']
+	
+    def release_access(self, tb, aid):
+	"""
+	Release access to testbed through fedd
+	"""
+
+	uri = self.tbmap.get(tb, None)
+	if not uri:
+	    raise service_error(serice_error.server_config, 
+		    "Unknown testbed: %s" % tb)
+
+	resp = self.call_ReleaseAccess(uri, {'allocID': aid},
+		self.cert_file, self.cert_pwd, self.trusted_certs)
+
+	# better error coding
+
+    def remote_splitter(self, uri, desc, master):
+
+	req = {
+		'description' : { 'ns2description': desc },
+		'master': master,
+		'include_fedkit': bool(self.fedkit)
+	    }
+
+	r = self.call_Ns2Split(uri, req, self.cert_file, self.cert_pwd, 
+		self.trusted_certs)
+
+	if r.has_key('Ns2SplitResponseBody'):
+	    r = r['Ns2SplitResponseBody']
+	    if r.has_key('output'):
+		return r['output'].splitlines()
+	    else:
+		raise service_error(service_error.protocol, 
+			"Bad splitter response (no output)")
+	else:
+	    raise service_error(service_error.protocol, "Bad splitter response")
+	
+    class current_testbed:
+	"""
+	Object for collecting the current testbed description.  The testbed
+	description is saved to a file with the local testbed variables
+	subsittuted line by line.
+	"""
+	def __init__(self, eid, tmpdir, fedkit):
+	    self.begin_testbed = re.compile("^#\s+Begin\s+Testbed\s+\((\w+)\)")
+	    self.end_testbed = re.compile("^#\s+End\s+Testbed\s+\((\w+)\)")
+	    self.current_testbed = None
+	    self.testbed_file = None
+
+	    self.def_expstart = \
+		    "sudo -H /bin/sh FEDDIR/fed_bootstrap >& /tmp/federate";
+	    self.def_mexpstart = "sudo -H FEDDIR/make_hosts FEDDIR/hosts";
+	    self.def_gwstart = \
+		    "sudo -H FEDDIR/fed-tun.pl -f GWCONF>& /tmp/bridge.log";
+	    self.def_mgwstart = \
+		    "sudo -H FEDDIR/fed-tun.pl -f GWCONF >& /tmp/bridge.log";
+	    self.def_gwimage = "FBSD61-TUNNEL2";
+	    self.def_gwtype = "pc";
+
+	    self.eid = eid
+	    self.tmpdir = tmpdir
+	    self.fedkit = fedkit
+
+	def __call__(self, line, master, allocated, tbparams):
+	    # Capture testbed topology descriptions
+	    if self.current_testbed == None:
+		m = self.begin_testbed.match(line)
+		if m != None:
+		    self.current_testbed = m.group(1)
+		    if self.current_testbed == None:
+			raise service_error(service_error.req,
+				"Bad request format (unnamed testbed)")
+		    allocated[self.current_testbed] = \
+			    allocated.get(self.current_testbed,0) + 1
+		    tb_dir = "%s/%s" % (self.tmpdir, self.current_testbed)
+		    if not os.path.exists(tb_dir):
+			try:
+			    os.mkdir(tb_dir)
+			except IOError:
+			    raise service_error(service_error.internal,
+				    "Cannot create %s" % tb_dir)
+		    try:
+			self.testbed_file = open("%s/%s.%s.tcl" %
+				(tb_dir, self.eid, self.current_testbed), 'w')
+		    except IOError:
+			self.testbed_file = None
+		    return True
+		else: return False
+	    else:
+		m = self.end_testbed.match(line)
+		if m != None:
+		    if m.group(1) != self.current_testbed:
+			raise service_error(service_error.internal, 
+				"Mismatched testbed markers!?")
+		    if self.testbed_file != None: 
+			self.testbed_file.close()
+			self.testbed_file = None
+		    self.current_testbed = None
+		elif self.testbed_file:
+		    # Substitute variables and put the line into the local
+		    # testbed file.
+		    gwtype = tbparams[self.current_testbed].get('gwtype', 
+			    self.def_gwtype)
+		    gwimage = tbparams[self.current_testbed].get('gwimage', 
+			    self.def_gwimage)
+		    mgwstart = tbparams[self.current_testbed].get('mgwstart', 
+			    self.def_mgwstart)
+		    mexpstart = tbparams[self.current_testbed].get('mexpstart', 
+			    self.def_mexpstart)
+		    gwstart = tbparams[self.current_testbed].get('gwstart', 
+			    self.def_gwstart)
+		    expstart = tbparams[self.current_testbed].get('expstart', 
+			    self.def_expstart)
+		    project = tbparams[self.current_testbed].get('project')
+		    line = re.sub("GWTYPE", gwtype, line)
+		    line = re.sub("GWIMAGE", gwimage, line)
+		    if self.current_testbed == master:
+			line = re.sub("GWSTART", mgwstart, line)
+			line = re.sub("EXPSTART", mexpstart, line)
+		    else:
+			line = re.sub("GWSTART", gwstart, line)
+			line = re.sub("EXPSTART", expstart, line)
+		    line = re.sub("GWCONF", "FEDDIR`hostname`.gw.conf", line)
+		    line = re.sub("PROJDIR", "/proj/%s/" % project, line)
+		    line = re.sub("EID", self.eid, line)
+		    line = re.sub("FEDDIR", "/proj/%s/exp/%s/tmp/" % \
+			    (project, self.eid), line)
+		    if self.fedkit:
+			line = re.sub("FEDKIT", os.path.basename(self.fedkit),
+				line)
+		    print >>self.testbed_file, line
+		return True
+
+    class allbeds:
+	"""
+	Process the Allbeds section.  Get access to each federant and save the
+	parameters in tbparams
+	"""
+	def __init__(self, get_access):
+	    self.begin_allbeds = re.compile("^#\s+Begin\s+Allbeds")
+	    self.end_allbeds = re.compile("^#\s+End\s+Allbeds")
+	    self.in_allbeds = False
+	    self.get_access = get_access
+
+	def __call__(self, line, user, tbparams, master, export_project,
+		access_user):
+	    # Testbed access parameters
+	    if not self.in_allbeds:
+		if self.begin_allbeds.match(line):
+		    self.in_allbeds = True
+		    return True
+		else:
+		    return False
+	    else:
+		if self.end_allbeds.match(line):
+		    self.in_allbeds = False
+		else:
+		    nodes = line.split('|')
+		    tb = nodes.pop(0)
+		    self.get_access(tb, nodes, user, tbparams, master,
+			    export_project, access_user)
+		return True
+
+    class gateways:
+	def __init__(self, eid, master, tmpdir, gw_pubkey,
+		gw_secretkey, copy_file, fedkit):
+	    self.begin_gateways = \
+		    re.compile("^#\s+Begin\s+gateways\s+\((\w+)\)")
+	    self.end_gateways = re.compile("^#\s+End\s+gateways\s+\((\w+)\)")
+	    self.current_gateways = None
+	    self.control_gateway = None
+	    self.active_end = { }
+
+	    self.eid = eid
+	    self.master = master
+	    self.tmpdir = tmpdir
+	    self.gw_pubkey_base = gw_pubkey
+	    self.gw_secretkey_base = gw_secretkey
+
+	    self.copy_file = copy_file
+	    self.fedkit = fedkit
+
+
+	def gateway_conf_file(self, gw, master, eid, pubkey, privkey,
+		active_end, tbparams, dtb, myname, desthost, type):
+	    """
+	    Produce a gateway configuration file from a gateways line. 
+	    """
+
+	    sproject = tbparams[gw].get('project', 'project')
+	    dproject = tbparams[dtb].get('project', 'project')
+	    sdomain = ".%s.%s%s" % (eid, sproject,
+		    tbparams[gw].get('domain', ".example.com"))
+	    ddomain = ".%s.%s%s" % (eid, dproject,
+		    tbparams[dtb].get('domain', ".example.com"))
+	    boss = tbparams[master].get('boss', "boss")
+	    fs = tbparams[master].get('fs', "fs")
+	    event_server = "%s%s" % \
+		    (tbparams[gw].get('eventserver', "event_server"),
+			    tbparams[gw].get('domain', "example.com"))
+	    remote_event_server = "%s%s" % \
+		    (tbparams[dtb].get('eventserver', "event_server"),
+			    tbparams[dtb].get('domain', "example.com"))
+	    seer_control = "%s%s" % \
+		    (tbparams[gw].get('control', "control"), sdomain)
+
+	    if self.fedkit:
+		remote_script_dir = "/usr/local/federation/bin"
+		local_script_dir = "/usr/local/federation/bin"
+	    else:
+		remote_script_dir = "/proj/%s/exp/%s/tmp" % ( dproject, eid)
+		local_script_dir = "/proj/%s/exp/%s/tmp" % ( sproject, eid)
+
+	    local_key_dir = "/proj/%s/exp/%s/tmp" % ( sproject, eid)
+	    remote_conf_dir = "/proj/%s/exp/%s/tmp" % ( dproject, eid)
+	    tunnel_cfg = tbparams[gw].get("tun", "false")
+
+	    conf_file = "%s%s.gw.conf" % (myname, sdomain)
+	    remote_conf_file = "%s%s.gw.conf" % (desthost, ddomain)
+
+	    # translate to lower case so the `hostname` hack for specifying
+	    # configuration files works.
+	    conf_file = conf_file.lower();
+	    remote_conf_file = remote_conf_file.lower();
+
+	    if dtb == master:
+		active = "false"
+	    elif gw == master:
+		active = "true"
+	    elif active_end.has_key['%s-%s' % (dtb, gw)]:
+		active = "false"
+	    else:
+		active_end['%s-%s' % (gw, dtb)] = 1
+		active = "true"
+
+	    gwconfig = open("%s/%s/%s" % (self.tmpdir, gw, conf_file), "w")
+	    print >>gwconfig, "Active: %s" % active
+	    print >>gwconfig, "TunnelCfg: %s" % tunnel_cfg
+	    print >>gwconfig, "BossName: %s" % boss
+	    print >>gwconfig, "FsName: %s" % fs
+	    print >>gwconfig, "EventServerName: %s" % event_server
+	    print >>gwconfig, "RemoteEventServerName: %s" % remote_event_server
+	    print >>gwconfig, "SeerControl: %s" % seer_control
+	    print >>gwconfig, "Type: %s" % type
+	    print >>gwconfig, "RemoteScriptDir: %s" % remote_script_dir
+	    print >>gwconfig, "EventRepeater: %s/fed_evrepeater" % \
+		    local_script_dir
+	    print >>gwconfig, "RemoteExperiment: %s/%s" % (dproject, eid)
+	    print >>gwconfig, "LocalExperiment: %s/%s" % (sproject, eid)
+	    print >>gwconfig, "RemoteConfigFile: %s/%s" % \
+		    (remote_conf_dir, remote_conf_file)
+	    print >>gwconfig, "Peer: %s%s" % (desthost, ddomain)
+	    print >>gwconfig, "Pubkeys: %s/%s" % (local_key_dir, pubkey)
+	    print >>gwconfig, "Privkeys: %s/%s" % (local_key_dir, privkey)
+	    gwconfig.close()
+
+	    return active == "true"
+
+	def __call__(self, line, allocated, tbparams):
+	    # Process gateways
+	    if not self.current_gateways:
+		m = self.begin_gateways.match(line)
+		if m:
+		    self.current_gateways = m.group(1)
+		    if allocated.has_key(self.current_gateways):
+			# This test should always succeed
+			tb_dir = "%s/%s" % (self.tmpdir, self.current_gateways)
+			if not os.path.exists(tb_dir):
+			    try:
+				os.mkdir(tb_dir)
+			    except IOError:
+				raise service_error(service_error.internal,
+					"Cannot create %s" % tb_dir)
+		    else:
+			# XXX
+			self.log.error("[gateways]: Ignoring gateways for " + \
+				"unknown testbed %s" % self.current_gateways)
+			self.current_gateways = None
+		    return True
+		else:
+		    return False
+	    else:
+		m = self.end_gateways.match(line)
+		if m :
+		    if m.group(1) != self.current_gateways:
+			raise service_error(service_error.internal,
+				"Mismatched gateway markers!?")
+		    if self.control_gateway:
+			try:
+			    cc = open("%s/%s/client.conf" %
+				    (self.tmpdir, self.current_gateways), 'w')
+			    print >>cc, "ControlGateway: %s" % \
+				    self.control_gateway
+			    if tbparams[self.master].has_key('smbshare'):
+				print >>cc, "SMBSHare: %s" % \
+					tbparams[self.master]['smbshare']
+			    print >>cc, "ProjectUser: %s" % \
+				    tbparams[self.master]['user']
+			    print >>cc, "ProjectName: %s" % \
+				    tbparams[self.master]['project']
+			    cc.close()
+			except IOError:
+			    raise service_error(service_error.internal,
+				    "Error creating client config")
+			try:
+			    cc = open("%s/%s/seer.conf" %
+				    (self.tmpdir, self.current_gateways),
+				    'w')
+			    if self.current_gateways != self.master:
+				print >>cc, "ControlNode: %s" % \
+					self.control_gateway
+			    print >>cc, "ExperimentID: %s/%s" % \
+				    ( tbparams[self.master]['project'], \
+				    self.eid )
+			    cc.close()
+			except IOError:
+			    raise service_error(service_error.internal,
+				    "Error creating seer config")
+		    else:
+			debug.error("[gateways]: No control gateway for %s" %\
+				    self.current_gateways)
+		    self.current_gateways = None
+		else:
+		    dtb, myname, desthost, type = line.split(" ")
+
+		    if type == "control" or type == "both":
+			self.control_gateway = "%s.%s.%s%s" % (myname, 
+				self.eid, 
+				tbparams[self.current_gateways]['project'],
+				tbparams[self.current_gateways]['domain'])
+		    try:
+			active = self.gateway_conf_file(self.current_gateways,
+				self.master, self.eid, self.gw_pubkey_base,
+				self.gw_secretkey_base,
+				self.active_end, tbparams, dtb, myname,
+				desthost, type)
+		    except IOError, e:
+			raise service_error(service_error.internal,
+				"Failed to write config file for %s" % \
+					self.current_gateway)
+	    
+		    gw_pubkey = "%s/keys/%s" % \
+			    (self.tmpdir, self.gw_pubkey_base)
+		    gw_secretkey = "%s/keys/%s" % \
+			    (self.tmpdir, self.gw_secretkey_base)
+
+		    pkfile = "%s/%s/%s" % \
+			    ( self.tmpdir, self.current_gateways, 
+				    self.gw_pubkey_base)
+		    skfile = "%s/%s/%s" % \
+			    ( self.tmpdir, self.current_gateways, 
+				    self.gw_secretkey_base)
+
+		    if not os.path.exists(pkfile):
+			try:
+			    self.copy_file(gw_pubkey, pkfile)
+			except IOError:
+			    service_error(service_error.internal,
+				    "Failed to copy pubkey file")
+
+		    if active and not os.path.exists(skfile):
+			try:
+			    self.copy_file(gw_secretkey, skfile)
+			except IOError:
+			    service_error(service_error.internal,
+				    "Failed to copy secretkey file")
+		return True
+
+    class shunt_to_file:
+	"""
+	Simple class to write data between two regexps to a file.
+	"""
+	def __init__(self, begin, end, filename):
+	    """
+	    Begin shunting on a match of begin, stop on end, send data to
+	    filename.
+	    """
+	    self.begin = re.compile(begin)
+	    self.end = re.compile(end)
+	    self.in_shunt = False
+	    self.file = None
+	    self.filename = filename
+
+	def __call__(self, line):
+	    """
+	    Call this on each line in the input that may be shunted.
+	    """
+	    if not self.in_shunt:
+		if self.begin.match(line):
+		    self.in_shunt = True
+		    try:
+			self.file = open(self.filename, "w")
+		    except:
+			self.file = None
+			raise
+		    return True
+		else:
+		    return False
+	    else:
+		if self.end.match(line):
+		    if self.file: 
+			self.file.close()
+			self.file = None
+		    self.in_shunt = False
+		else:
+		    if self.file:
+			print >>self.file, line
+		return True
+
+    class shunt_to_list:
+	"""
+	Same interface as shunt_to_file.  Data collected in self.list, one list
+	element per line.
+	"""
+	def __init__(self, begin, end):
+	    self.begin = re.compile(begin)
+	    self.end = re.compile(end)
+	    self.in_shunt = False
+	    self.list = [ ]
+	
+	def __call__(self, line):
+	    if not self.in_shunt:
+		if self.begin.match(line):
+		    self.in_shunt = True
+		    return True
+		else:
+		    return False
+	    else:
+		if self.end.match(line):
+		    self.in_shunt = False
+		else:
+		    self.list.append(line)
+		return True
+
+    class shunt_to_string:
+	"""
+	Same interface as shunt_to_file.  Data collected in self.str, all in
+	one string.
+	"""
+	def __init__(self, begin, end):
+	    self.begin = re.compile(begin)
+	    self.end = re.compile(end)
+	    self.in_shunt = False
+	    self.str = ""
+	
+	def __call__(self, line):
+	    if not self.in_shunt:
+		if self.begin.match(line):
+		    self.in_shunt = True
+		    return True
+		else:
+		    return False
+	    else:
+		if self.end.match(line):
+		    self.in_shunt = False
+		else:
+		    self.str += line
+		return True
+
+    def create_experiment(self, req, fid):
+	"""
+	The external interface to experiment creation called from the
+	dispatcher.
+
+	Creates a working directory, splits the incoming description using the
+	splitter script and parses out the avrious subsections using the
+	lcasses above.  Once each sub-experiment is created, use pooled threads
+	to instantiate them and start it all up.
+	"""
+
+	if not self.auth.check_attribute(fid, 'create'):
+	    raise service_error(service_error.access, "Create access denied")
+
+	try:
+	    tmpdir = tempfile.mkdtemp(prefix="split-")
+	except IOError:
+	    raise service_error(service_error.internal, "Cannot create tmp dir")
+
+	gw_pubkey_base = "fed.%s.pub" % self.ssh_type
+	gw_secretkey_base = "fed.%s" % self.ssh_type
+	gw_pubkey = tmpdir + "/keys/" + gw_pubkey_base
+	gw_secretkey = tmpdir + "/keys/" + gw_secretkey_base
+	tclfile = tmpdir + "/experiment.tcl"
+	tbparams = { }
+	try:
+	    access_user = self.accessdb[fid]
+	except KeyError:
+	    raise service_error(service_error.internal,
+		    "Access map and authorizer out of sync in " + \
+			    "create_experiment for fedid %s"  % fid)
+
+	pid = "dummy"
+	gid = "dummy"
+	# XXX
+	fail_soft = False
+
+	try:
+	    os.mkdir(tmpdir+"/keys")
+	except OSError:
+	    raise service_error(service_error.internal,
+		    "Can't make temporary dir")
+
+	req = req.get('CreateRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no CreateRequestBody)")
+	# The tcl parser needs to read a file so put the content into that file
+	descr=req.get('experimentdescription', None)
+	if descr:
+	    file_content=descr.get('ns2description', None)
+	    if file_content:
+		try:
+		    f = open(tclfile, 'w')
+		    f.write(file_content)
+		    f.close()
+		except IOError:
+		    raise service_error(service_error.internal,
+			    "Cannot write temp experiment description")
+	    else:
+		raise service_error(service_error.req, 
+			"Only ns2descriptions supported")
+	else:
+	    raise service_error(service_error.req, "No experiment description")
+
+	if req.has_key('experimentID') and \
+		req['experimentID'].has_key('localname'):
+	    eid = req['experimentID']['localname']
+	    self.state_lock.acquire()
+	    while (self.state.has_key(eid)):
+		eid += random.choice(string.ascii_letters)
+	    # To avoid another thread picking this localname
+	    self.state[eid] = "placeholder"
+	    self.state_lock.release()
+	else:
+	    eid = self.exp_stem
+	    for i in range(0,5):
+		eid += random.choice(string.ascii_letters)
+	    self.state_lock.acquire()
+	    while (self.state.has_key(eid)):
+		eid = self.exp_stem
+		for i in range(0,5):
+		    eid += random.choice(string.ascii_letters)
+	    # To avoid another thread picking this localname
+	    self.state[eid] = "placeholder"
+	    self.state_lock.release()
+
+	try: 
+	    # This catches exceptions to clear the placeholder if necessary
+	    try:
+		self.generate_ssh_keys(gw_secretkey, self.ssh_type)
+	    except ValueError:
+		raise service_error(service_error.server_config, 
+			"Bad key type (%s)" % self.ssh_type)
+
+	    user = req.get('user', None)
+	    if user == None:
+		raise service_error(service_error.req, "No user")
+
+	    master = req.get('master', None)
+	    if not master:
+		raise service_error(service_error.req,
+			"No master testbed label")
+	    export_project = req.get('exportProject', None)
+	    if not export_project:
+		raise service_error(service_error.req, "No export project")
+	    
+	    if self.splitter_url:
+		self.log.debug("Calling remote splitter at %s" % \
+			self.splitter_url)
+		split_data = self.remote_splitter(self.splitter_url,
+			file_content, master)
+	    else:
+		tclcmd = [self.tclsh, self.tcl_splitter, '-s', '-x', 
+		    str(self.muxmax), '-m', master]
+
+		if self.fedkit:
+		    tclcmd.append('-k')
+
+		tclcmd.extend([pid, gid, eid, tclfile])
+
+		self.log.debug("running local splitter %s", " ".join(tclcmd))
+		tclparser = Popen(tclcmd, stdout=PIPE)
+		split_data = tclparser.stdout
+
+	    allocated = { }	    # Testbeds we can access
+	    started = { }	    # Testbeds where a sub-experiment started 
+				# successfully
+
+	    # Objects to parse the splitter output (defined above)
+	    parse_current_testbed = self.current_testbed(eid, tmpdir,
+		    self.fedkit)
+	    parse_allbeds = self.allbeds(self.get_access)
+	    parse_gateways = self.gateways(eid, master, tmpdir,
+		    gw_pubkey_base, gw_secretkey_base, self.copy_file,
+		    self.fedkit)
+	    parse_vtopo = self.shunt_to_string("^#\s+Begin\s+Vtopo",
+			"^#\s+End\s+Vtopo")
+	    parse_hostnames = self.shunt_to_file("^#\s+Begin\s+hostnames",
+			"^#\s+End\s+hostnames", tmpdir + "/hosts")
+	    parse_tarfiles = self.shunt_to_list("^#\s+Begin\s+tarfiles",
+		    "^#\s+End\s+tarfiles")
+	    parse_rpms = self.shunt_to_list("^#\s+Begin\s+rpms",
+		    "^#\s+End\s+rpms")
+
+	    # Working on the split data
+	    for line in split_data:
+		line = line.rstrip()
+		if parse_current_testbed(line, master, allocated, tbparams):
+		    continue
+		elif parse_allbeds(line, user, tbparams, master, export_project,
+			access_user):
+		    continue
+		elif parse_gateways(line, allocated, tbparams):
+		    continue
+		elif parse_vtopo(line):
+		    continue
+		elif parse_hostnames(line):
+		    continue
+		elif parse_tarfiles(line):
+		    continue
+		elif parse_rpms(line):
+		    continue
+		else:
+		    raise service_error(service_error.internal, 
+			    "Bad tcl parse? %s" % line)
+	    # Virtual topology and visualization
+	    vtopo = self.gentopo(parse_vtopo.str)
+	    if not vtopo:
+		raise service_error(service_error.internal, 
+			"Failed to generate virtual topology")
+
+	    vis = self.genviz(vtopo)
+	    if not vis:
+		raise service_error(service_error.internal, 
+			"Failed to generate visualization")
+	    
+	    # save federant information
+	    for k in allocated.keys():
+		tbparams[k]['federant'] = {\
+			'name': [ { 'localname' : eid} ],\
+			'emulab': tbparams[k]['emulab'],\
+			'allocID' : tbparams[k]['allocID'],\
+			'master' : k == master,\
+		    }
+
+
+	    # Copy tarfiles and rpms needed at remote sites into a staging area
+	    try:
+		if self.fedkit:
+		    parse_tarfiles.list.append(self.fedkit)
+		for t in parse_tarfiles.list:
+		    if not os.path.exists("%s/tarfiles" % tmpdir):
+			os.mkdir("%s/tarfiles" % tmpdir)
+		    self.copy_file(t, "%s/tarfiles/%s" % \
+			    (tmpdir, os.path.basename(t)))
+		for r in parse_rpms.list:
+		    if not os.path.exists("%s/rpms" % tmpdir):
+			os.mkdir("%s/rpms" % tmpdir)
+		    self.copy_file(r, "%s/rpms/%s" % \
+			    (tmpdir, os.path.basename(r)))
+	    except IOError, e:
+		raise service_error(service_error.internal, 
+			"Cannot stage tarfile/rpm: %s" % e.strerror)
+
+	except service_error, e:
+	    # If something goes wrong in the parse (usually an access error)
+	    # clear the placeholder state.  From here on out the code delays
+	    # exceptions.
+	    self.state_lock.acquire()
+	    del self.state[eid]
+	    self.state_lock.release()
+	    raise e
+
+	thread_pool_info = self.thread_pool()
+	threads = [ ]
+
+	for tb in [ k for k in allocated.keys() if k != master]:
+	    # Wait until we have a free slot to start the next testbed load
+	    thread_pool_info.acquire()
+	    while thread_pool_info.started - \
+		    thread_pool_info.terminated >= self.nthreads:
+		thread_pool_info.wait()
+	    thread_pool_info.release()
+
+	    # Create and start a thread to start the segment, and save it to
+	    # get the return value later
+	    t  = self.pooled_thread(target=self.start_segment, 
+		    args=(tb, eid, tbparams, tmpdir, 0), name=tb,
+		    pdata=thread_pool_info, trace_file=self.trace_file)
+	    threads.append(t)
+	    t.start()
+
+	# Wait until all finish (the first clause of the while is to make sure
+	# one starts)
+	thread_pool_info.acquire()
+	while thread_pool_info.started == 0 or \
+		thread_pool_info.started > thread_pool_info.terminated:
+	    thread_pool_info.wait()
+	thread_pool_info.release()
+
+	# If none failed, start the master
+	failed = [ t.getName() for t in threads if not t.rv ]
+
+	if len(failed) == 0:
+	    if not self.start_segment(master, eid, tbparams, tmpdir):
+		failed.append(master)
+
+	succeeded = [tb for tb in allocated.keys() if tb not in failed]
+	# If one failed clean up, unless fail_soft is set
+	if failed:
+	    if not fail_soft:
+		for tb in succeeded:
+		    self.stop_segment(tb, eid, tbparams)
+		# Remove the placeholder
+		self.state_lock.acquire()
+		del self.state[eid]
+		self.state_lock.release()
+
+		raise service_error(service_error.federant,
+		    "Swap in failed on %s" % ",".join(failed))
+	else:
+	    self.log.info("[start_segment]: Experiment %s started" % eid)
+
+	# Generate an ID for the experiment (slice) and a certificate that the
+	# allocator can use to prove they own it.  We'll ship it back through
+	# the encrypted connection.
+	(expid, expcert) = generate_fedid("test", dir=tmpdir, log=self.log)
+
+	self.log.debug("[start_experiment]: removing %s" % tmpdir)
+
+	# Walk up tmpdir, deleting as we go
+	for path, dirs, files in os.walk(tmpdir, topdown=False):
+	    for f in files:
+		os.remove(os.path.join(path, f))
+	    for d in dirs:
+		os.rmdir(os.path.join(path, d))
+	os.rmdir(tmpdir)
+
+	# The deepcopy prevents the allocation ID and other binaries from being
+	# translated into other formats
+	resp = { 'federant' : [ copy.deepcopy(tbparams[tb]['federant']) \
+		for tb in tbparams.keys() \
+		    if tbparams[tb].has_key('federant') ],\
+		    'vtopo': vtopo,\
+		    'vis' : vis,
+		    'experimentID' : [\
+			    { 'fedid': copy.copy(expid) }, \
+			    { 'localname': eid },\
+			],\
+		    'experimentAccess': { 'X509' : expcert },\
+		}
+
+	# Insert the experiment into our state and update the disk copy
+	self.state_lock.acquire()
+	self.state[expid] = { 'federant' : [ tbparams[tb]['federant'] \
+		for tb in tbparams.keys() \
+		    if tbparams[tb].has_key('federant') ],\
+		    'vtopo': vtopo,\
+		    'vis' : vis,
+		    'owner': fid,
+		    'experimentID' : [\
+			    { 'fedid': expid }, { 'localname': eid },\
+			],\
+		}
+	self.state[eid] = self.state[expid]
+	if self.state_filename: self.write_state()
+	self.state_lock.release()
+
+	self.auth.set_attribute(fid, expid)
+	self.auth.set_attribute(expid, expid)
+
+	if not failed:
+	    return resp
+	else:
+	    raise service_error(service_error.partial, \
+		    "Partial swap in on %s" % ",".join(succeeded))
+
+    def check_experiment_access(self, fid, key):
+	"""
+	Confirm that the fid has access to the experiment.  Though a request
+	may be made in terms of a local name, the access attribute is always
+	the experiment's fedid.
+	"""
+	if not isinstance(key, fedid):
+	    self.state_lock.acquire()
+	    if self.state.has_key(key):
+		try:
+		    kl = [ f['fedid'] for f in self.state[key]['experimentID']\
+			    if f.has_key('fedid') ]
+		except KeyError:
+		    self.state_lock.release()
+		    raise service_error(service_error.internal, 
+			    "No fedid for experiment %s when checking " +\
+				    "access(!?)" % key)
+		if len(kl) == 1:
+		    key = kl[0]
+		else:
+		    self.state_lock.release()
+		    raise service_error(service_error.internal, 
+			    "multiple fedids for experiment %s when " +\
+				    "checking access(!?)" % key)
+	    else:
+		self.state_lock.release()
+		raise service_error(service_error.access, "Access Denied")
+	    self.state_lock.release()
+
+	if self.auth.check_attribute(fid, key):
+	    return True
+	else:
+	    raise service_error(service_error.access, "Access Denied")
+
+
+
+    def get_vtopo(self, req, fid):
+	"""
+	Return the stored virtual topology for this experiment
+	"""
+	rv = None
+
+	req = req.get('VtopoRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no VtopoRequestBody)")
+	exp = req.get('experiment', None)
+	if exp:
+	    if exp.has_key('fedid'):
+		key = exp['fedid']
+		keytype = "fedid"
+	    elif exp.has_key('localname'):
+		key = exp['localname']
+		keytype = "localname"
+	    else:
+		raise service_error(service_error.req, "Unknown lookup type")
+	else:
+	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
+
+	self.state_lock.acquire()
+	if self.state.has_key(key):
+	    rv = { 'experiment' : {keytype: key },\
+		    'vtopo': self.state[key]['vtopo'],\
+		}
+	self.state_lock.release()
+
+	if rv: return rv
+	else: raise service_error(service_error.req, "No such experiment")
+
+    def get_vis(self, req, fid):
+	"""
+	Return the stored visualization for this experiment
+	"""
+	rv = None
+
+	req = req.get('VisRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no VisRequestBody)")
+	exp = req.get('experiment', None)
+	if exp:
+	    if exp.has_key('fedid'):
+		key = exp['fedid']
+		keytype = "fedid"
+	    elif exp.has_key('localname'):
+		key = exp['localname']
+		keytype = "localname"
+	    else:
+		raise service_error(service_error.req, "Unknown lookup type")
+	else:
+	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
+
+	self.state_lock.acquire()
+	if self.state.has_key(key):
+	    rv =  { 'experiment' : {keytype: key },\
+		    'vis': self.state[key]['vis'],\
+		    }
+	self.state_lock.release()
+
+	if rv: return rv
+	else: raise service_error(service_error.req, "No such experiment")
+
+    def get_info(self, req, fid):
+	"""
+	Return all the stored info about this experiment
+	"""
+	rv = None
+
+	req = req.get('InfoRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no VisRequestBody)")
+	exp = req.get('experiment', None)
+	if exp:
+	    if exp.has_key('fedid'):
+		key = exp['fedid']
+		keytype = "fedid"
+	    elif exp.has_key('localname'):
+		key = exp['localname']
+		keytype = "localname"
+	    else:
+		raise service_error(service_error.req, "Unknown lookup type")
+	else:
+	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
+
+	# The state may be massaged by the service function that called
+	# get_info (e.g., encoded for XMLRPC transport) so send a copy of the
+	# state.
+	self.state_lock.acquire()
+	if self.state.has_key(key):
+	    rv = copy.deepcopy(self.state[key])
+	self.state_lock.release()
+
+	if rv: return rv
+	else: raise service_error(service_error.req, "No such experiment")
+
+
+    def terminate_experiment(self, req, fid):
+	"""
+	Swap this experiment out on the federants and delete the shared
+	information
+	"""
+	tbparams = { }
+	req = req.get('TerminateRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no TerminateRequestBody)")
+	exp = req.get('experiment', None)
+	if exp:
+	    if exp.has_key('fedid'):
+		key = exp['fedid']
+		keytype = "fedid"
+	    elif exp.has_key('localname'):
+		key = exp['localname']
+		keytype = "localname"
+	    else:
+		raise service_error(service_error.req, "Unknown lookup type")
+	else:
+	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
+
+	self.state_lock.acquire()
+	fed_exp = self.state.get(key, None)
+
+	if fed_exp:
+	    # This branch of the conditional holds the lock to generate a
+	    # consistent temporary tbparams variable to deallocate experiments.
+	    # It releases the lock to do the deallocations and reacquires it to
+	    # remove the experiment state when the termination is complete.
+	    ids = []
+	    #  experimentID is a list of dicts that are self-describing
+	    #  identifiers.  This finds all the fedids and localnames - the
+	    #  keys of self.state - and puts them into ids.
+	    for id in fed_exp.get('experimentID', []):
+		if id.has_key('fedid'): ids.append(id['fedid'])
+		if id.has_key('localname'): ids.append(id['localname'])
+
+	    # Construct enough of the tbparams to make the stop_segment calls
+	    # work
+	    for fed in fed_exp['federant']:
+		try:
+		    for e in fed['name']:
+			eid = e.get('localname', None)
+			if eid: break
+		    else:
+			continue
+
+		    p = fed['emulab']['project']
+
+		    project = p['name']['localname']
+		    tb = p['testbed']['localname']
+		    user = p['user'][0]['userID']['localname']
+
+		    domain = fed['emulab']['domain']
+		    host  = "%s%s" % (fed['emulab']['ops'], domain)
+		    aid = fed['allocID']
+		except KeyError, e:
+		    continue
+		tbparams[tb] = {\
+			'user': user,\
+			'domain': domain,\
+			'project': project,\
+			'host': host,\
+			'eid': eid,\
+			'aid': aid,\
+		    }
+	    self.state_lock.release()
+
+	    # Stop everyone.
+	    for tb in tbparams.keys():
+		self.stop_segment(tb, tbparams[tb]['eid'], tbparams)
+
+	    # release the allocations
+	    for tb in tbparams.keys():
+		self.release_access(tb, tbparams[tb]['aid'])
+
+	    # Remove the terminated experiment
+	    self.state_lock.acquire()
+	    for id in ids:
+		if self.state.has_key(id): del self.state[id]
+
+	    if self.state_filename: self.write_state()
+	    self.state_lock.release()
+
+	    return { 'experiment': exp }
+	else:
+	    # Don't forget to release the lock
+	    self.state_lock.release()
+	    raise service_error(service_error.req, "No saved state")
Index: fedd/federation/fedid.py
===================================================================
--- fedd/federation/fedid.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/fedid.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,176 @@
+#!/usr/local/bin/python
+
+import os, sys
+import subprocess
+import tempfile
+import logging
+
+from M2Crypto import SSL, X509, EVP
+from pyasn1.codec.der import decoder
+
+# The version of M2Crypto on users is pretty old and doesn't have all the
+# features that are useful.  The legacy code is somewhat more brittle than the
+# main line, but will work.
+if "as_der" not in dir(EVP.PKey):
+    from asn1_raw import get_key_bits_from_file, get_key_bits_from_cert
+    legacy = True
+else:
+    legacy = False
+
+class fedid:
+    """
+    Wrapper around the federated ID from an X509 certificate. 
+    """
+    HASHSIZE=20
+    def __init__(self, bits=None, hexstr=None, cert=None, file=None):
+	if bits != None:
+	    self.set_bits(bits)
+	elif hexstr != None:
+	    self.set_hexstr(hexstr)
+	elif cert != None:
+	    self.set_cert(cert)
+	elif file != None:
+	    self.set_file(file)
+	else:
+	    self.buf = None
+
+    def __hash__(self):
+	return hash(self.buf)
+
+    def __eq__(self, other):
+	if isinstance(other, type(self)):
+	    return self.buf == other.buf
+	elif isinstance(other, type(str())):
+	    return self.buf == other;
+	else:
+	    return False
+
+    def __ne__(self, other): return not self.__eq__(other)
+
+    def __str__(self):
+	if self.buf != None:
+	    return str().join([ "%02x" % ord(x) for x in self.buf])
+	else: return ""
+
+    def __repr__(self):
+	return "fedid(hexstr='%s')" % self.__str__()
+
+    def pack_soap(self):
+	return self.buf
+
+    def pack_xmlrpc(self):
+	return self.buf
+
+    def digest_bits(self, bits):
+	"""Internal function.  Compute the fedid from bits and store in buf"""
+	d = EVP.MessageDigest('sha1')
+	d.update(bits)
+	self.buf = d.final()
+
+
+    def set_hexstr(self, hexstr):
+	h = hexstr.replace(':','')
+	self.buf= str().join([chr(int(h[i:i+2],16)) \
+		for i in range(0,2*fedid.HASHSIZE,2)])
+
+    def get_hexstr(self):
+	"""Return the hexstring representation of the fedid"""
+	return __str__(self)
+
+    def set_bits(self, bits):
+	"""Set the fedid to bits(a 160 bit buffer)"""
+	self.buf = bits
+
+    def get_bits(self):
+	"""Get the 160 bit buffer from the fedid"""
+	return self.buf
+
+    def set_file(self, file):
+	"""Get the fedid from a certificate file
+
+	Calculate the SHA1 hash over the bit string of the public key as
+	defined in RFC3280.
+	"""
+	self.buf = None
+	if legacy: self.digest_bits(get_key_bits_from_file(file))
+	else: self.set_cert(X509.load_cert(file))
+
+    def set_cert(self, cert):
+	"""Get the fedid from a certificate.
+
+	Calculate the SHA1 hash over the bit string of the public key as
+	defined in RFC3280.
+	"""
+
+	self.buf = None
+	if (cert != None):
+	    if legacy:
+		self.digest_bits(get_key_bits_from_cert(cert))
+	    else:
+		b = []
+		k = cert.get_pubkey()
+
+		# Getting the key was easy, but getting the bit string of the
+		# key requires a side trip through ASN.1
+		dec = decoder.decode(k.as_der())
+
+		# kv is a tuple of the bits in the key.  The loop below
+		# recombines these into bytes and then into a buffer for the
+		# SSL digest function.
+		kv =  dec[0].getComponentByPosition(1)
+		for i in range(0, len(kv), 8):
+		    v = 0
+		    for j in range(0, 8):
+			v = (v << 1) + kv[i+j]
+		    b.append(v)
+		# The comprehension turns b from a list of bytes into a buffer
+		# (string) of bytes
+		self.digest_bits(str().join([chr(x) for x in b]))
+
+def generate_fedid(subj, bits=2048, log=None, dir=None, trace=sys.stderr,
+	ssl_prog="/usr/bin/openssl"):
+    """
+    Create a new certificate and derive a fedid from it.
+
+    The fedid and the certificate are returned as a tuple.
+    """
+
+    keypath = None
+    certpath = None
+    try:
+	try:
+	    kd, keypath = tempfile.mkstemp(dir=dir, prefix="key",
+		    suffix=".pem")
+	    cd, certpath = tempfile.mkstemp(dir=dir, prefix="cert",
+		    suffix=".pem")
+
+	    cmd = [ssl_prog, "req", "-text", "-newkey", 
+		    "rsa:%d" % bits, "-keyout", keypath,  "-nodes", 
+		    "-subj", "/CN=%s" % subj, "-x509", "-days", "30", 
+		    "-out", certpath]
+
+	    if log:
+		log.debug("[generate_fedid] %s" % " ".join(cmd))
+
+	    if trace: call_out = trace
+	    else: 
+		call_out = open("/dev/null", "w")
+
+	    rv = subprocess.call(cmd, stdout=call_out, stderr=call_out)
+	    log.debug("rv = %d" % rv)
+	    if rv == 0:
+		cert = ""
+		for p in (certpath, keypath):
+		    f = open(p)
+		    for line in f:
+			cert += line
+		
+		fid = fedid(file=certpath)
+		return (fid, cert)
+	    else:
+		return (None, None)
+	except IOError, e:
+	    raise e
+    finally:
+	if keypath: os.remove(keypath)
+	if certpath: os.remove(certpath)
Index: fedd/federation/fixed_resource.py
===================================================================
--- fedd/federation/fixed_resource.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/fixed_resource.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,49 @@
+#!/usr/local/bin/python
+
+# Encode the set of fixed keys as a colon-separated user/key pairs.  This also
+# includes a command line utility to manipulate the DB.
+
+def read_key_db(file):
+    """
+    Read the set of fixed keys fom the file
+    """
+    keys = set()
+
+    f = open(file, "r")
+    for line in f:
+	u, k = line.rstrip().split(':', 2)
+	keys.add((u, k))
+    f.close()
+    return keys
+
+def write_key_db(file, keys):
+    """
+    Write the set of keys to the given file
+    """
+
+    f = open(file, 'w')
+    for t in keys:
+	print >>f, "%s:%s" % t
+    f.close()
+
+def read_project_db(file):
+    """
+    Read the set of fixed keys fom the file
+    """
+    projects = set()
+
+    f = open(file, "r")
+    for line in f:
+	projects.add(line.rstrip())
+    f.close()
+    return projects
+
+def write_project_db(file, projects):
+    """
+    Write the set of keys to the given file
+    """
+
+    f = open(file, 'w')
+    for p in projects:
+	print >>f, "%s" % p
+    f.close()
Index: fedd/federation/parse_detail.py
===================================================================
--- fedd/federation/parse_detail.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/parse_detail.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,39 @@
+#!/usr/local/bin/python
+
+from fedd_services import *
+
+from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
+from ZSI.wstools.Namespaces import SOAP
+from ZSI.fault import FaultType
+
+
+# This is a bit of minimal trickery to integrate detail parsing of SOAP faults.
+# ZSI expects detail fields to parse to a class with an "any" attribute.  This
+# little class has such an attribute pointed at itself, so when
+# ZSI.fault.FaultFromMessage extracts that field, it places this object into
+# the new Fault object.  One can also get around this using an extra XML field
+# in the message, parsing the wrapper to the any attribute and then actial body
+# to the class in the any attribute, but this does not perturb the WSDL.
+class detail_faultType:
+    def __init__(self):
+	self.FeddFaultBody = None
+	self.any = self
+    def get_element_FeddFaultBody(self):
+	return self.FeddFaultBody
+
+# Override ZSI.fault.FaultType.typecode - give a parser that speaks our
+# language.
+FaultType.typecode = \
+    Struct(FaultType,
+    [QName(pname='faultcode'),
+    String(pname='faultstring'),
+    URI(pname=(SOAP.ENV,'faultactor'), minOccurs=0),
+    Struct(pname='detail', aname='detail', 
+	ofwhat=[ns0.faultType_Def((ns0.faultType_Def.schema, "FeddFaultBody"))],
+	pyclass=detail_faultType, minOccurs=0),
+    ],  
+    pname=(SOAP.ENV,'Fault'),
+    inline=True,
+    hasextras=0,
+)
+
Index: fedd/federation/remote_service.py
===================================================================
--- fedd/federation/remote_service.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/remote_service.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,460 @@
+#!/usr/local/bin/python
+
+import copy
+
+from socket import error as socket_error
+
+import M2Crypto.httpslib
+from M2Crypto import SSL
+from M2Crypto.m2xmlrpclib import SSL_Transport
+from M2Crypto.SSL import SSLError
+from ZSI import ParseException, FaultException, SoapWriter
+
+from service_error import service_error
+from xmlrpclib import ServerProxy, dumps, loads, Fault, Error, Binary
+
+import fedd_services
+import fedd_internal_services
+from util import fedd_ssl_context
+from fedid import fedid
+import parse_detail
+
+# Turn off the matching of hostname to certificate ID
+SSL.Connection.clientPostConnectionCheck = None
+
+# Used by the remote_service_base class.
+def to_binary(o):
+    """
+    A function that converts an object into an xmlrpclib.Binary using
+    either its internal packing method, or the standard Binary constructor.
+    """
+    pack = getattr(o, 'pack_xmlrpc', None)
+    if callable(pack): return Binary(pack())
+    else: return Binary(o)
+
+# Classes that encapsulate the process of making and dealing with requests to
+# WSDL-generated and XMLRPC remote accesses.  
+
+class remote_service_base:
+    """
+    This invisible base class encapsulates the functions used to massage the
+    dictionaries used to pass parameters into and out of the RPC formats.  It's
+    mostly a container for the static methods to do that work, but defines some
+    maps sued by sub classes on apply_to_tags
+    """
+    # A map used to convert fedid fields to fedid objects (when the field is
+    # already a string)
+    fedid_to_object = ( ('fedid', lambda x: fedid(bits=x)),)
+    # A map used by apply_to_tags to convert fedids from xmlrpclib.Binary
+    # objects to fedid objects in one sweep.
+    decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
+    # A map used to encapsulate fedids into xmlrpclib.Binary objects
+    encap_fedids = (('fedid', to_binary),)
+
+    @staticmethod
+    def pack_soap(container, name, contents):
+	"""
+	Convert the dictionary in contents into a tree of ZSI classes.
+
+	The holder classes are constructed from factories in container and
+	assigned to either the element or attribute name.  This is used to
+	recursively create the SOAP message.
+	"""
+	if getattr(contents, "__iter__", None) != None:
+	    attr =getattr(container, "new_%s" % name, None)
+	    if attr: obj = attr()
+	    else:
+		raise TypeError("%s does not have a new_%s attribute" % \
+			(container, name))
+	    for e, v in contents.iteritems():
+		assign = getattr(obj, "set_element_%s" % e, None) or \
+			getattr(obj, "set_attribute_%s" % e, None)
+		if isinstance(v, type(dict())):
+		    assign(remote_service_base.pack_soap(obj, e, v))
+		elif getattr(v, "__iter__", None) != None:
+		    assign([ remote_service_base.pack_soap(obj, e, val ) \
+			    for val in v])
+		elif getattr(v, "pack_soap", None) != None:
+		    assign(v.pack_soap())
+		else:
+		    assign(v)
+	    return obj
+	else: return contents
+
+    @staticmethod
+    def unpack_soap(element):
+	"""
+	Convert a tree of ZSI SOAP classes intro a hash.  The inverse of
+	pack_soap
+
+	Elements or elements that are empty are ignored.
+	"""
+	methods = [ m for m in dir(element) \
+		if m.startswith("get_element") or m.startswith("get_attribute")]
+	if len(methods) > 0:
+	    rv = { }
+	    for m in methods:
+		if m.startswith("get_element_"):
+		    n = m.replace("get_element_","",1)
+		else:
+		    n = m.replace("get_attribute_", "", 1)
+		sub = getattr(element, m)()
+		if sub != None:
+		    if isinstance(sub, basestring):
+			rv[n] = sub
+		    elif getattr(sub, "__iter__", None) != None:
+			if len(sub) > 0: rv[n] = \
+				[remote_service_base.unpack_soap(e) \
+				    for e in sub]
+		    else:
+			rv[n] = remote_service_base.unpack_soap(sub)
+	    return rv
+	else: 
+	    return element
+
+    @staticmethod
+    def apply_to_tags(e, map):
+	"""
+	Map is an iterable of ordered pairs (tuples) that map a key to a
+	function.
+	This function walks the given message and replaces any object with a
+	key in the map with the result of applying that function to the object.
+	"""
+	if isinstance(e, dict):
+	    for k in e.keys():
+		for tag, fcn in map:
+		    if k == tag:
+			if isinstance(e[k], list):
+			    e[k] = [ fcn(b) for b in e[k]]
+			else:
+			    e[k] = fcn(e[k])
+		    elif isinstance(e[k], dict):
+			remote_service_base.apply_to_tags(e[k], map)
+		    elif isinstance(e[k], list):
+			for ee in e[k]:
+			    remote_service_base.apply_to_tags(ee, map)
+	# Other types end the recursion - they should be leaves
+	return e
+
+    @staticmethod
+    def strip_unicode(obj):
+	"""Walk through a message and convert all strings to non-unicode
+	strings"""
+	if isinstance(obj, dict):
+	    for k in obj.keys():
+		obj[k] = remote_service_base.strip_unicode(obj[k])
+	    return obj
+	elif isinstance(obj, basestring) and not isinstance(obj, str):
+	    return str(obj)
+	elif getattr(obj, "__iter__", None):
+	    return [ remote_service_base.strip_unicode(x) for x in obj]
+	else:
+	    return obj
+
+    @staticmethod
+    def make_unicode(obj):
+	"""Walk through a message and convert all strings to unicode"""
+	if isinstance(obj, dict):
+	    for k in obj.keys():
+		obj[k] = remote_service_base.make_unicode(obj[k])
+	    return obj
+	elif isinstance(obj, basestring) and not isinstance(obj, unicode):
+	    return unicode(obj)
+	elif getattr(obj, "__iter__", None):
+	    return [ remote_service_base.make_unicode(x) for x in obj]
+	else:
+	    return obj
+
+
+
+class soap_handler(remote_service_base):
+    """
+    Encapsulate the handler code to unpack and pack SOAP requests and responses
+    and call the given method.
+
+    The code to decapsulate and encapsulate parameters encoded in SOAP is the
+    same modulo a few parameters.  This is a functor that calls a fedd service
+    trhough a soap interface.  The parameters are the typecode of the request
+    parameters, the method to call (usually a bound instance of a method on a
+    fedd service providing class), the constructor of a response packet and the
+    name of the body element of that packet.  The handler takes a ParsedSoap
+    object (the request) and returns an instance of the class created by
+    constructor containing the response.  Failures of the constructor or badly
+    created constructors will result in None being returned.
+    """
+    def __init__(self, service_name, method, typecode=None,
+	    constructor=None, body_name=None):
+	self.method = method
+
+	response_class_name = "%sResponseMessage" % service_name
+	request_class_name = "%sRequestMessage" % service_name
+
+	if body_name: self.body_name = body_name
+	else: self.body_name = "%sResponseBody" % service_name
+
+	if constructor: self.constructor = constructor
+	else:
+	    self.constructor = self.get_class(response_class_name)
+	    if not self.constructor:
+		raise service_error(service_error.internal,
+			"Cannot find class for %s" % response_class_name)
+
+	if typecode: self.typecode = typecode
+	else: 
+	    req = self.get_class(request_class_name)
+	    if req:
+		self.typecode = req.typecode
+	    else:
+		raise service_error(service_error.internal,
+			"Cannot find class for %s" % request_class_name)
+
+	    if not self.typecode:
+		raise service_error(service_error.internal,
+			"Cannot get typecode for %s" % class_name)
+
+    def get_class(self, class_name):
+	return getattr(fedd_services, class_name, None) or \
+		getattr(fedd_internal_services, class_name, None)
+
+    def __call__(self, ps, fid):
+	req = ps.Parse(self.typecode)
+	# Convert the message to a dict with the fedid strings converted to
+	# fedid objects
+	req = self.apply_to_tags(self.unpack_soap(req), self.fedid_to_object)
+
+	msg = self.method(req, fid)
+
+	resp = self.constructor()
+	set_element = getattr(resp, "set_element_%s" % self.body_name, None)
+	if set_element and callable(set_element):
+	    try:
+		set_element(self.pack_soap(resp, self.body_name, msg))
+		return resp
+	    except (NameError, TypeError):
+		return None
+	else:
+	    return None
+
+class xmlrpc_handler(remote_service_base):
+    """
+    Generate the handler code to unpack and pack XMLRPC requests and responses
+    and call the given method.
+
+    The code to marshall and unmarshall XMLRPC parameters to and from a fedd
+    service is largely the same.  This helper creates such a handler.  The
+    parameters are the method name, and the name of the body struct that
+    contains the response.  A handler is created that takes the params response
+    from an xmlrpclib.loads on the incoming rpc and a fedid and responds with
+    a hash representing the struct ro be returned to the other side.  On error
+    None is returned.  Fedid fields are decapsulated from binary and converted
+    to fedid objects on input and encapsulated as Binaries on output.
+    """
+    def __init__(self, service_name, method):
+	self.method = method
+	self.body_name = "%sResponseBody" % service_name
+
+    def __call__(self, params, fid):
+	msg = None
+
+	p = self.apply_to_tags(params[0], self.decap_fedids)
+	try:
+	    msg = self.method(p, fid)
+	except service_error, e:
+	    raise Fault(e.code_string(), e.desc)
+	if msg != None:
+	    return self.make_unicode(self.apply_to_tags(\
+		    { self.body_name: msg }, self.encap_fedids))
+	else:
+	    return None
+
+class service_caller(remote_service_base):
+    def __init__(self, service_name, request_message=None, 
+	    request_body_name=None, tracefile=None):
+	self.service_name = service_name
+
+	if getattr(fedd_services.feddBindingSOAP, service_name, None):
+	    self.locator = fedd_services.feddServiceLocator
+	    self.port_name = 'getfeddPortType'
+	elif getattr(fedd_internal_services.feddInternalBindingSOAP, 
+		service_name, None):
+	    self.locator = fedd_internal_services.feddInternalServiceLocator
+	    self.port_name = 'getfeddInternalPortType'
+
+	if request_message: self.request_message = request_message
+	else:
+	    request_message_name = "%sRequestMessage" % service_name
+	    self.request_message = \
+		    getattr(fedd_services, request_message_name, None) or \
+		    getattr(fedd_internal_services, request_message_name, None)
+	    if not self.request_message:
+		raise service_error(service_error.internal,
+			"Cannot find class for %s" % request_message_name)
+
+	if request_body_name: self.request_body_name = request_body_name
+	else: self.request_body_name = "%sRequestBody" % service_name
+
+	self.tracefile = tracefile
+	self.__call__ = self.call_service
+
+    def serialize_soap(self, req):
+	"""
+	Return a string containing the message that would be sent to call this
+	service with the given request.
+	"""
+	msg = self.request_message()
+	set_element = getattr(msg, "set_element_%s" % self.request_body_name,
+		None)
+	if not set_element:
+	    raise service_error(service_error.internal,
+		    "Cannot get element setting method for %s" % \
+			    self.request_body_name)
+	set_element(self.pack_soap(msg, self.request_body_name, req))
+	sw = SoapWriter()
+	sw.serialize(msg)
+	return unicode(sw)
+
+    def call_xmlrpc_service(self, url, req, cert_file=None, cert_pwd=None, 
+	    trusted_certs=None, context=None, tracefile=None):
+	"""Send an XMLRPC request.  """
+
+
+	# If a context is given, use it.  Otherwise construct one from
+	# components.  The construction shouldn't call out for passwords.
+	if context:
+	    ctx = context
+	else:
+	    try:
+		ctx = fedd_ssl_context(cert_file, trusted_certs, 
+			password=cert_pwd)
+	    except SSL.SSLError:
+		raise service_error(service_error.server_config,
+			"Certificates misconfigured")
+
+	# Of all the dumbass things.  The XMLRPC library in use here won't
+	# properly encode unicode strings, so we make a copy of req with
+	# the unicode objects converted.  We also convert the url to a
+	# basic string if it isn't one already.
+	r = self.strip_unicode(copy.deepcopy(req))
+	url = str(url)
+	
+	transport = SSL_Transport(ctx)
+	port = ServerProxy(url, transport=transport)
+	# Make the call, and convert faults back to service_errors
+	try:
+	    remote_method = getattr(port, self.service_name, None)
+	    resp = remote_method(self.apply_to_tags(\
+		    { self.request_body_name: r}, self.encap_fedids))
+	except socket_error, e:
+	    raise service_error(service_error.protocol, 
+		    "Cannot connect to %s: %s" % (url, e[1]))
+	except SSLError, e:
+	    raise service_error(service_error.protocol,
+		    "SSL error contacting %s: %s" % (url, e.message))
+	except Fault, f:
+	    raise service_error(None, f.faultString, f.faultCode)
+	except Error, e:
+	    raise service_error(service_error.protocol, 
+		    "Remote XMLRPC Fault: %s" % e)
+
+	return self.apply_to_tags(resp, self.decap_fedids) 
+
+    def call_soap_service(self, url, req, cert_file=None, cert_pwd=None,
+	    trusted_certs=None, context=None, tracefile=None):
+	"""
+	Send req on to the real destination in dt and return the response
+
+	Req is just the requestType object.  This function re-wraps it.  It
+	also rethrows any faults.
+	"""
+
+	tf = tracefile or self.tracefile or None
+
+	# If a context is given, use it.  Otherwise construct one from
+	# components.  The construction shouldn't call out for passwords.
+	if context:
+	    ctx = context
+	else:
+	    try:
+		ctx = fedd_ssl_context(cert_file, trusted_certs, 
+			password=cert_pwd)
+	    except SSL.SSLError:
+		raise service_error(service_error.server_config,
+			"Certificates misconfigured")
+	loc = self.locator()
+	get_port = getattr(loc, self.port_name, None)
+	if not get_port:
+	    raise service_error(service_error.internal, 
+		    "Cannot get port %s from locator" % self.port_name)
+	port = get_port(url,
+		transport=M2Crypto.httpslib.HTTPSConnection, 
+		transdict={ 'ssl_context' : ctx },
+		tracefile=tf)
+	remote_method = getattr(port, self.service_name, None)
+	if not remote_method:
+	    raise service_error(service_error.internal,
+		    "Cannot get service from SOAP port")
+
+	# Reconstruct the full request message
+	msg = self.request_message()
+	set_element = getattr(msg, "set_element_%s" % self.request_body_name,
+		None)
+	if not set_element:
+	    raise service_error(service_error.internal,
+		    "Cannot get element setting method for %s" % \
+			    self.request_body_name)
+	set_element(self.pack_soap(msg, self.request_body_name, req))
+	try:
+	    resp = remote_method(msg)
+	except socket_error, e:
+	    raise service_error(service_error.protocol, 
+		    "Cannot connect to %s: %s" % (url, e[1]))
+	except SSLError, e:
+	    raise service_error(service_error.protocol,
+		    "SSL error contacting %s: %s" % (url, e.message))
+	except ParseException, e:
+	    raise service_error(service_error.protocol,
+		    "Bad format message (XMLRPC??): %s" % e)
+	except FaultException, e:
+	    ee = self.unpack_soap(e.fault.detail[0]).get('FeddFaultBody', { })
+	    if ee:
+		raise service_error(ee['code'], ee['desc'])
+	    else:
+		raise service_error(service_error.internal,
+			"Unexpected fault body")
+	# Unpack and convert fedids to objects
+	r = self.apply_to_tags(self.unpack_soap(resp), self.fedid_to_object)
+	#  Make sure all strings are unicode
+	r = self.make_unicode(r)
+	return r
+
+    def call_service(self, url, req, cert_file=None, cert_pwd=None, 
+	trusted_certs=None, context=None, tracefile=None):
+	p_fault = None	# Any SOAP failure (sent unless XMLRPC works)
+	resp = None
+	try:
+	    # Try the SOAP request
+	    resp = self.call_soap_service(url, req, 
+		    cert_file, cert_pwd, trusted_certs, context, tracefile)
+	    return resp
+	except service_error, e:
+	    if e.code == service_error.protocol: p_fault = None
+	    else: raise
+	except FaultException, f:
+	    p_fault = f.fault.detail[0]
+		
+
+	# If we could not get a valid SOAP response to the request above,
+	# try the same address using XMLRPC and let any faults flow back
+	# out.
+	if p_fault == None:
+	    resp = self.call_xmlrpc_service(url, req, cert_file,
+		    cert_pwd, trusted_certs, context, tracefile)
+	    return resp
+	else:
+	    # Build the fault
+	    ee = unpack_soap(p_fault).get('FeddFaultBody', { })
+	    if ee:
+		raise service_error(ee['code'], ee['desc'])
+	    else:
+		raise service_error(service_error.internal,
+			"Unexpected fault body")
Index: fedd/federation/server.py
===================================================================
--- fedd/federation/server.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/server.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,223 @@
+#!/usr/local/bin/python
+
+import sys
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+
+from ZSI import Fault, ParseException, FaultFromNotUnderstood, \
+    FaultFromZSIException, FaultFromException, ParsedSoap, SoapWriter
+
+from M2Crypto import SSL
+from M2Crypto.SSL.SSLServer import ThreadingSSLServer
+import xmlrpclib
+
+from fedid import fedid
+from fedd_services import ns0
+from service_error import *
+
+import logging
+
+# The SSL server here is based on the implementation described at
+# http://www.xml.com/pub/a/ws/2004/01/20/salz.html
+
+# Turn off the matching of hostname to certificate ID
+SSL.Connection.clientPostConnectionCheck = None
+
+class server(ThreadingSSLServer):
+    """
+    Interface the fedd services to the XMLRPC and SOAP interfaces
+    """
+    def __init__(self, ME, handler, ssl_ctx, impl):
+	"""
+	Create an SSL server that handles the transport in handler using the
+	credentials in ssl_ctx, and interfacing to the implementation of fedd
+	services in fedd.  ME is the host port pair on which to bind.
+	"""
+	ThreadingSSLServer.__init__(self, ME, handler, ssl_ctx)
+	self.impl = impl
+	self.soap_methods = impl.soap_services
+	self.xmlrpc_methods = impl.xmlrpc_services
+	self.log = logging.getLogger("fedd")
+
+    def ahandle_error(self, request, address):
+	"""
+	The default SSLServer prints a stack trace here.  This is a little
+	friendlier.
+	"""
+	if request or address:
+	    self.log.warn("[fedd] Error on incoming connection: %s %s" % \
+		    (request, address))
+	else:
+	    self.log.warn("[fedd] Error on incoming connection " + \
+		    "(Likely SSL error)")
+
+class soap_handler(BaseHTTPRequestHandler):
+    """
+    Standard connection between SOAP and the fedd services in impl.
+
+    Much of this is boilerplate from 
+    http://www.xml.com/pub/a/ws/2004/01/20/salz.html
+    """
+    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
+
+    def send_xml(self, text, code=200):
+	"""Send an XML document as reply"""
+	self.send_response(code)
+	self.send_header('Content-type', 'text/xml; charset="utf-8"')
+	self.send_header('Content-Length', str(len(text)))
+	self.end_headers()
+	self.wfile.write(text)
+	self.wfile.flush()
+	self.request.socket.close()
+
+    def send_fault(self, f, code=500):
+	"""Send a SOAP encoded fault as reply"""
+	self.send_xml(f.AsSOAP(processContents="lax"), code)
+
+    def check_headers(self, ps):
+	"""Send a fault for any required envelope headers"""
+	for (uri, localname) in ps.WhatMustIUnderstand():
+	    self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd'))
+	    return False
+	return  True
+
+    def check_method(self, ps):
+	"""Confirm that this class implements the namespace and SOAP method"""
+	root = ps.body_root
+	if root.namespaceURI not in self.server.impl.soap_namespaces:
+	    self.send_fault(Fault(Fault.Client, 
+		'Unknown namespace "%s"' % root.namespaceURI))
+	    return False
+
+	if getattr(root, 'localName', None) == None:
+	    self.send_fault(Fault(Fault.Client, 'No method"'))
+	    return False
+	return True
+
+    def do_POST(self):
+	"""Treat an HTTP POST request as a SOAP service call"""
+	try:
+	    cl = int(self.headers['content-length'])
+	    data = self.rfile.read(cl)
+	    ps = ParsedSoap(data)
+	except ParseException, e:
+	    self.send_fault(Fault(Fault.Client, str(e)))
+	    return
+	except Exception, e:
+	    self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
+	    return
+	if not self.check_headers(ps): return
+	if not self.check_method(ps): return
+	try:
+	    resp = self.soap_dispatch(ps.body_root.localName, ps,
+		    fedid(cert=self.request.get_peer_cert()))
+	except Fault, f:
+	    self.send_fault(f)
+	    resp = None
+	
+	if resp != None:
+	    sw = SoapWriter()
+	    sw.serialize(resp)
+	    self.send_xml(str(sw))
+
+    def log_request(self, code=0, size=0):
+	"""
+	Log request to the fedd logger
+	"""
+	self.server.log.info("Successful SOAP request code %d" % code)
+
+    def soap_dispatch(self, method, req, fid):
+	"""
+	The connection to the implementation, using the  method maps
+
+	The implementation provides a mapping from SOAP method name to the
+	method in the implementation that provides the service.
+	"""
+	if self.server.soap_methods.has_key(method):
+	    try:
+		return self.server.soap_methods[method](req, fid)
+	    except service_error, e:
+		de = ns0.faultType_Def(
+			(ns0.faultType_Def.schema,
+			    "FeddFaultBody")).pyclass()
+		de._code=e.code
+		de._errstr=e.code_string()
+		de._desc=e.desc
+		if  e.is_server_error():
+		    raise Fault(Fault.Server, e.code_string(), detail=de)
+		else:
+		    raise Fault(Fault.Client, e.code_string(), detail=de)
+	else:
+	    raise Fault(Fault.Client, "Unknown method: %s" % method)
+
+
+class xmlrpc_handler(BaseHTTPRequestHandler):
+    """
+    Standard connection between XMLRPC and the fedd services in impl.
+
+    Much of this is boilerplate from 
+    http://www.xml.com/pub/a/ws/2004/01/20/salz.html
+    """
+    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
+
+    def send_xml(self, text, code=200):
+	"""Send an XML document as reply"""
+	self.send_response(code)
+	self.send_header('Content-type', 'text/xml; charset="utf-8"')
+	self.send_header('Content-Length', str(len(text)))
+	self.end_headers()
+	self.wfile.write(text)
+	self.wfile.flush()
+	# Make sure to close the socket when we're done
+	self.request.socket.close()
+
+    def do_POST(self):
+	"""Treat an HTTP POST request as an XMLRPC service call"""
+	# NB: XMLRPC faults are not HTTP errors, so the code is always 200,
+	# unless an HTTP error occurs, which we don't handle.
+
+	resp = None
+	data = None
+	method = None
+	cl = int(self.headers['content-length'])
+	data = self.rfile.read(cl)
+
+	try:
+	    params, method = xmlrpclib.loads(data)
+	except xmlrpclib.ResponseError:
+	    data = xmlrpclib.dumps(xmlrpclib.Fault("Client", 
+		"Malformed request"), methodresponse=True)
+
+	if method != None:
+	    try:
+		resp = self.xmlrpc_dispatch(method, params,
+			    fedid(cert=self.request.get_peer_cert()))
+		data = xmlrpclib.dumps((resp,), encoding='UTF-8', 
+			methodresponse=True)
+	    except xmlrpclib.Fault, f:
+		data = xmlrpclib.dumps(f, methodresponse=True)
+		resp = None
+
+	self.send_xml(data)
+
+    def log_request(self, code=0, size=0):
+	"""
+	Log request to the fedd logger
+	"""
+	self.server.log.info("Successful XMLRPC request code %d" % code)
+
+
+    def xmlrpc_dispatch(self, method, req, fid):
+	"""
+	The connection to the implementation, using the  method maps
+
+	The implementation provides a mapping from XMLRPC method name to the
+	method in the implementation that provides the service.
+	"""
+	if self.server.xmlrpc_methods.has_key(method):
+	    try:
+		return self.server.xmlrpc_methods[method](req, fid)
+	    except service_error, e:
+		raise xmlrpclib.Fault(e.code_string(), e.desc)
+	else:
+	    raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
Index: fedd/federation/service_error.py
===================================================================
--- fedd/federation/service_error.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/service_error.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,46 @@
+#!/usr/local/bin/python
+
+# This is used to make the service error reporting independent of the
+# transport.  The XMLRPC and SOAP dispatchers will convert it into
+# transport-specific errors
+class service_error(RuntimeError):
+    access = 1
+    protocol= 2
+    req = 3
+    server_config = 4
+    internal = 5
+    partial = 6
+    federant = 7
+    code_str = { 
+	access : "Access Denied",
+	protocol : "Protocol Error",
+	req : "Badly Formed Request",
+	server_config: "Server Configuration Error",
+	internal : "Internal Error",
+	partial: "Partial embedding",
+	federant: "Federant error"
+    }
+    str_code = dict([ (v, k) for k, v in code_str.iteritems() ])
+    client_errors = ( req )
+    server_errors = ( access, protocol, server_config, internal)
+
+    def __init__(self, code=None, desc=None, from_string=None):
+	self.code = code
+	self.desc = desc
+	if code == None:
+	    self.set_code_from_string(from_string)
+	RuntimeError.__init__(self, desc)
+
+    def code_string(self, code=None):
+	code = code or self.code
+	return service_error.code_str.get(code)
+    
+    def set_code_from_string(self, errstr):
+	self.code = service_error.str_code.get(errstr, service_error.internal)
+	return self.code
+    
+    def is_client_error(self):
+	return self.code in service_error.client_errors
+
+    def is_server_error(self):
+	return self.code in service_error.server_errors
Index: fedd/federation/split.py
===================================================================
--- fedd/federation/split.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/split.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,154 @@
+#!/usr/local/bin/python
+
+import os,sys
+import subprocess
+import tempfile
+import logging
+
+from util import *
+from fedid import fedid
+from remote_service import xmlrpc_handler, soap_handler
+from service_error import *
+from authorizer import authorizer
+
+
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.splitter")
+fl.addHandler(nullHandler())
+
+class split_local:
+    def __init__(self, config=None, auth=None):
+	"""
+	Intialize the various attributes, most from the config object
+	"""
+	self.debug = config.getboolean("splitter", "debug")
+	self.muxmax = config.getint("splitter", "muxmax", 3)
+	self.tclsh = config.get("splitter", "tclsh", 
+		"/usr/local/bin/otclsh")
+	self.tcl_splitter = config.get("splitter", "tcl_splitter",
+		"/usr/testbed/lib/ns2ir/parse.tcl")
+	access_db = config.get("splitter", "access_db", None)
+	allow_any = config.getboolean("splitter", "allow_any", False)
+
+	self.log = logging.getLogger("fedd.splitter")
+	set_log_level(config, "splitter", self.log)
+	self.trace_file = sys.stderr
+
+	set_log_level(config, "splitter", self.log)
+
+	if auth:
+	    self.auth= auth
+	else:
+	    self.auth = authorizer()
+	    self.log.warning("[splitter] No authorizer passed in, " +\
+		    "using local one")
+
+	if access_db and allow_any: 
+	    raise service_error(service_error.internal, 
+		    "Cannot specify both an access database and allow_any " +\
+			    "for splitter")
+	
+	if access_db:
+	    try:
+		read_simple_accessdb(access_db, self.auth, 'split')
+	    except IOError, e:
+		raise service_error(service_error.internal,
+			"Error reading accessDB %s: %s" % (access_db, e))
+	    except ValueError:
+		raise service_error(service_error.internal, "%s" % e)
+	elif allow_any:
+	    auth.set_global_attribute("split")
+
+
+	# Dispatch tables
+	self.soap_services = {\
+		'Ns2Split': soap_handler("Ns2Split", self.run_splitter),
+	}
+
+	self.xmlrpc_services = {\
+		'Ns2Split': xmlrpc_handler('Ns2Split', self.run_splitter),
+	}
+
+    def run_splitter(self, req, fid):
+	"""
+	The external interface to experiment creation called from the
+	dispatcher.
+
+	Creates a working directory, splits the incoming description using the
+	splitter script and parses out the avrious subsections using the
+	lcasses above.  Once each sub-experiment is created, use pooled threads
+	to instantiate them and start it all up.
+	"""
+
+	if not self.auth.check_attribute(fid, 'split'):
+	    raise service_error(service_error.access, "Access Denied")
+
+	try:
+	    tmpdir = tempfile.mkdtemp(prefix="split-")
+	except IOError:
+	    raise service_error(service_error.internal, "Cannot create tmp dir")
+
+	tclfile = tmpdir + "/experiment.tcl"
+	pid = "dummy"
+	gid = "dummy"
+	eid = "dummy"
+
+	req = req.get('Ns2SplitRequestBody', None)
+	if not req:
+	    raise service_error(service_error.req,
+		    "Bad request format (no Ns2SplitRequestBody)")
+	# The tcl parser needs to read a file so put the content into that file
+	descr=req.get('description', None)
+	if descr:
+	    file_content=descr.get('ns2description', None)
+	    if file_content:
+		try:
+		    f = open(tclfile, 'w')
+		    f.write(file_content)
+		    f.close()
+		except IOError:
+		    raise service_error(service_error.internal,
+			    "Cannot write temp experiment description")
+	    else:
+		raise service_error(service_error.req, 
+			"Only ns2descriptions supported")
+	else:
+	    raise service_error(service_error.req, "No description")
+
+	master = req.get('master', None)
+	if master == None:
+	    raise service_error(service_error.req, "No master testbed label")
+	
+	include_fedkit = req.get('include_fedkit', None)
+	if include_fedkit == None:
+	    raise service_error(service_error.req, 
+		    "No federation kit inclusion directive")
+	
+	
+	tclcmd = [self.tclsh, self.tcl_splitter, '-s', '-x', 
+	    str(self.muxmax), '-m', master]
+	if include_fedkit: tclcmd.append('-k')
+	tclcmd.extend([pid, gid, eid, tclfile])
+	self.log.debug("Calling splitter %s" % " ".join(tclcmd))
+	tclparser = subprocess.Popen(tclcmd, stdout=subprocess.PIPE)
+
+	out = ""
+	# Package up the split data
+	for line in tclparser.stdout:
+	    out += line
+
+	# Walk up tmpdir, deleting as we go
+	for path, dirs, files in os.walk(tmpdir, topdown=False):
+	    for f in files:
+		os.remove(os.path.join(path, f))
+	    for d in dirs:
+		os.rmdir(os.path.join(path, d))
+	os.rmdir(tmpdir)
+
+	resp = { 'output' : out }
+
+	return resp
+
+
Index: fedd/federation/util.py
===================================================================
--- fedd/federation/util.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
+++ fedd/federation/util.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -0,0 +1,116 @@
+#!/usr/local/bin/python
+
+import re
+import string
+import logging
+
+from M2Crypto import SSL
+from fedid import fedid
+
+class fedd_ssl_context(SSL.Context):
+    """
+    Simple wrapper around an M2Crypto.SSL.Context to initialize it for fedd.
+    """
+    def __init__(self, my_cert, trusted_certs=None, password=None):
+	"""
+	construct a fedd_ssl_context
+
+	@param my_cert: PEM file with my certificate in it
+	@param trusted_certs: PEM file with trusted certs in it (optional)
+	"""
+	SSL.Context.__init__(self)
+
+	# load_cert takes a callback to get a password, not a password, so if
+	# the caller provided a password, this creates a nonce callback using a
+	# lambda form.
+	if password != None and not callable(password):
+	    # This is cute.  password = lambda *args: password produces a
+	    # function object that returns itself rather than one that returns
+	    # the object itself.  This is because password is an object
+	    # reference and after the assignment it's a lambda.  So we assign
+	    # to a temp.
+	    pwd = password
+	    password =lambda *args: pwd
+
+	if password != None:
+	    self.load_cert(my_cert, callback=password)
+	else:
+	    self.load_cert(my_cert)
+
+	# If no trusted certificates are specified, allow unknown CAs.
+	if trusted_certs: 
+	    self.load_verify_locations(trusted_certs)
+	    self.set_verify(SSL.verify_peer, 10)
+	else:
+	    self.set_verify(SSL.verify_peer, 10, 
+		callback=SSL.cb.ssl_verify_callback_allow_unknown_ca)
+
+def read_simple_accessdb(fn, auth, mask=[]):
+    """
+    Read a simple access database.  Each line is a fedid (of the form
+    fedid:hexstring) and a comma separated list of atributes to be assigned to
+    it.  This parses out the fedids and adds the attributes to the authorizer.
+    comments (preceded with a #) and blank lines are ignored.  Exceptions (e.g.
+    file exceptions and ValueErrors from badly parsed lines) are propagated.
+    """
+
+    rv = [ ]
+    lineno = 0
+    fedid_line = re.compile("fedid:([" + string.hexdigits + "]+)\s+" +\
+	    "(\w+\s*(,\s*\w+)*)")
+
+    # If a single string came in, make it a list
+    if isinstance(mask, basestring): mask = [ mask ]
+
+    f = open(fn, 'r')
+    for line in f:
+	lineno += 1
+	line = line.strip()
+	if line.startswith('#') or len(line) == 0: 
+	    continue
+	m = fedid_line.match(line)
+	if m :
+	    fid = fedid(hexstr=m.group(1))
+	    for a in [ a.strip() for a in m.group(2).split(",") \
+		    if not mask or a.strip() in mask ]:
+		auth.set_attribute(fid, a.strip())
+	else:
+	    raise ValueError("Badly formatted line in accessdb: %s line %d" %\
+		    (nf, lineno))
+    f.close()
+    return rv
+	
+
+def pack_id(id):
+    """
+    Return a dictionary with the field name set by the id type.  Handy for
+    creating dictionaries to be converted to messages.
+    """
+    if isinstance(id, fedid): return { 'fedid': id }
+    elif id.startswith("http:") or id.startswith("https:"): return { 'uri': id }
+    else: return { 'localname': id}
+
+def unpack_id(id):
+    """return id as a type determined by the key"""
+    for k in ("localname", "fedid", "uri", "kerberosUsername"):
+	if id.has_key(k): return id[k]
+    return None
+
+def set_log_level(config, sect, log):
+    """ Set the logging level to the value passed in sect of config."""
+    # The getattr sleight of hand finds the logging level constant
+    # corrersponding to the string.  We're a little paranoid to avoid user
+    # mayhem.
+    if config.has_option(sect, "log_level"):
+	level_str = config.get(sect, "log_level")
+	try:
+	    level = int(getattr(logging, level_str.upper(), -1))
+
+	    if  logging.DEBUG <= level <= logging.CRITICAL:
+		log.setLevel(level)
+	    else:
+		log.error("Bad experiment_log value: %s" % level_str)
+
+	except ValueError:
+	    log.error("Bad experiment_log value: %s" % level_str)
+
Index: fedd/fedid.py
===================================================================
--- fedd/fedid.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/fedid.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -1,5 +1,5 @@
 #!/usr/local/bin/python
 
-from fedd import fedid
+from federation import fedid
 from optparse import OptionParser
 
Index: fedd/fixed_resource.py
===================================================================
--- fedd/fixed_resource.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/fixed_resource.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -3,5 +3,5 @@
 import sys
 import MySQLdb
-from fedd.fixed_resource import read_key_db, write_key_db, \
+from federation.fixed_resource import read_key_db, write_key_db, \
 	read_project_db, write project_db
 from optparse import OptionParser
Index: fedd/setup.py
===================================================================
--- fedd/setup.py	(revision f816079a31726de41a73459d7e877302ad9e12d3)
+++ fedd/setup.py	(revision 5d3f2390d0bf55a90f3cb9b8f7d6d5c25c8dbac9)
@@ -9,8 +9,8 @@
 	author_email='faber@isi.edu',
 	url='http://fedd.isi.deterlab.net',
-	packages=['fedd'],
+	packages=['federation'],
 	requires=['ZSI(>=2.0)', 'M2Crypto'],
-	provides=['fedd'],
-	scripts=['fedd.py', 'fedd_client.py', 'confirm_sshkey.py', 
+	provides=['federation'],
+	scripts=['fedd.py', 'fedd_client.py', 'fedid.py', 'confirm_sshkey.py', 
 	    'exp_access_db.py', 'fixed_resource.py'],
     )
