Index: fedd/federation/protogeni_access.py
===================================================================
--- fedd/federation/protogeni_access.py	(revision c119839a168ca2908841e5d9666def8a597e80d9)
+++ fedd/federation/protogeni_access.py	(revision c119839a168ca2908841e5d9666def8a597e80d9)
@@ -0,0 +1,1101 @@
+#!/usr/local/bin/python
+
+import os,sys
+import stat # for chmod constants
+import re
+import string
+import copy
+import pickle
+import logging
+import subprocess
+
+from threading import *
+
+from util import *
+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
+
+import httplib
+import tempfile
+from urlparse import urlparse
+
+import topdl
+import list_log
+import proxy_protogeni_segment
+
+
+# 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
+
+
+    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 not config: 
+            raise RunTimeError("No config to fedd.access")
+
+        self.project_priority = config.getboolean("access", "project_priority")
+        self.allow_proxy = config.getboolean("access", "allow_proxy")
+
+        self.domain = config.get("access", "domain")
+	self.certdir = config.get("access","certdir")
+	self.userconfdir = config.get("access","userconfdir")
+	self.userconfcmd = config.get("access","userconfcmd")
+	self.userconfurl = config.get("access","userconfurl")
+	self.ssh_port = config.get("access","ssh_port") or "22"
+	self.sshd = config.get("access","sshd")
+	self.sshd_config = config.get("access", "sshd_config")
+	self.create_debug = config.getboolean("access", "create_debug")
+	self.cleanup = not config.getboolean("access", "leave_tmpfiles")
+	self.access_type = config.get("access", "type")
+	self.staging_dir = config.get("access", "staging_dir") or "/tmp"
+	self.staging_host = config.get("access", "staging_host") \
+		or "ops.emulab.net"
+
+	self.ch_url = config.get("access", "ch_url")
+	self.sa_url = config.get("access", "sa_url")
+	self.cm_url = config.get("access", "cm_url")
+
+        self.attrs = { }
+        self.access = { }
+        self.restricted = [ ]
+        self.projects = { }
+        self.keys = { }
+        self.types = { }
+        self.allocation = { }
+        self.state = { 
+            'projects': self.projects,
+            'allocation' : self.allocation,
+            'keys' : self.keys,
+            'types': self.types
+        }
+        self.log = logging.getLogger("fedd.access")
+        set_log_level(config, "access", self.log)
+        self.state_lock = Lock()
+	# XXX: Configurable
+	self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig'))
+	self.imports = set(('SMB', 'seer', 'userconfig'))
+
+        if auth: self.auth = auth
+        else:
+            self.log.error(\
+                    "[access]: No authorizer initialized, creating local one.")
+            auth = authorizer()
+
+        tb = config.get('access', 'testbed')
+        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
+        else: self.testbed = [ ]
+
+        if config.has_option("access", "accessdb"):
+            self.read_access(config.get("access", "accessdb"))
+
+        self.state_filename = config.get("access", "access_state")
+	print "Calling read_state %s" % self.state_filename
+        self.read_state()
+
+        # Keep cert_file and cert_pwd coming from the same place
+        self.cert_file = config.get("access", "cert_file")
+        if self.cert_file:
+            self.sert_pwd = config.get("access", "cert_pw")
+        else:
+            self.cert_file = config.get("globals", "cert_file")
+            self.sert_pwd = config.get("globals", "cert_pw")
+
+        self.trusted_certs = config.get("access", "trusted_certs") or \
+                config.get("globals", "trusted_certs")
+
+	self.start_segment = proxy_protogeni_segment.start_segment
+	self.stop_segment = proxy_protogeni_segment.stop_segment
+
+	self.call_SetValue = service_caller('SetValue')
+	self.call_GetValue = service_caller('GetValue')
+
+        self.soap_services = {\
+            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
+            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
+            'StartSegment': soap_handler("StartSegment", self.StartSegment),
+            'TerminateSegment': soap_handler("TerminateSegment", 
+		self.TerminateSegment),
+            }
+        self.xmlrpc_services =  {\
+            'RequestAccess': xmlrpc_handler('RequestAccess',
+                self.RequestAccess),
+            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
+                self.ReleaseAccess),
+            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
+            'TerminateSegment': xmlrpc_handler('TerminateSegment',
+                self.TerminateSegment),
+            }
+
+    def read_access(self, config):
+        """
+        Read a configuration file and set internal parameters.
+
+        There are access lines of the
+        form (tb, proj, user) -> user that map the first tuple of
+        names to the user for 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:.  The user is the
+	ProtoGENI identity certificate.
+        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 + ")"
+
+        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
+                re.IGNORECASE)
+	access_str = '\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ \
+                key_name+'\s*\)\s*->\s*\(('+name_expr +')\s*,\s*('\
+			+ name_expr + ')\s*,\s*('+name_expr+')\s*,?\s*(' + \
+			name_expr+ ')?\)'
+        access_re = re.compile(access_str, 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
+
+            # Access line (t, p, u) -> (a, pw) line
+	    # XXX: you are here
+            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])
+                cert = auth_name(parse_name(m.group(4)))
+                user_name = auth_name(parse_name(m.group(5)))
+		ssh_key = unicode(m.group(6))
+		if m.group(6): pw = unicode(m.group(7))
+		else: pw = None
+
+                self.access[access_key] = (cert, user_name, ssh_key, pw)
+                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()
+
+# need this ?
+    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
+# need this ?
+
+    def write_state(self):
+        if 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)
+
+
+    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.
+        """
+        if self.state_filename:
+            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 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.state.get('allocation', {}).keys():
+                for o in self.state['allocation'][k].get('owners', []):
+                    self.auth.set_attribute(o, fedid(hexstr=k))
+		self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
+
+	    if self.allocation != self.state['allocation']:
+		self.allocation = self.state['allocation']
+
+    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
+        user (protoGENI cert).
+        
+        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)]
+            else:
+                if self.access.has_key(s):
+                    return self.access[s]
+        return 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
+	user_re = re.compile("user:\s(.*)")
+	project_re = re.compile("project:\s(.*)")
+
+	user = [ user_re.findall(x)[0] for x in req.get('credential', []) \
+		if user_re.match(x)]
+	project = [ project_re.findall(x)[0] \
+		for x in req.get('credential', []) \
+		    if project_re.match(x)]
+
+	if len(project) == 1: project = project[0]
+	elif len(project) == 0: project = None
+	else: 
+	    raise service_error(service_error.req, 
+		    "More than one project credential")
+
+
+        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 ProtoGENI credentials to use
+        found = self.find_access((tb, project, user))
+        
+        if found == None:
+            raise service_error(service_error.access,
+                    "Access denied - cannot map access")
+	return found, owners
+
+    def get_handler(self, path, fid):
+	self.log.info("Get handler %s %s" % (path, fid))
+	if self.auth.check_attribute(fid, path) and self.userconfdir:
+	    return ("%s/%s" % (self.userconfdir, path), "application/binary")
+	else:
+	    return (None, None)
+
+    def export_userconf(self, project):
+	dev_null = None
+	confid, confcert = generate_fedid("test", dir=self.userconfdir, 
+		log=self.log)
+	conffilename = "%s/%s" % (self.userconfdir, str(confid))
+	cf = None
+	try:
+	    cf = open(conffilename, "w")
+	    os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
+	except IOError, e:
+	    raise service_error(service_error.internal, 
+		    "Cannot create user configuration data")
+
+	try:
+	    dev_null = open("/dev/null", "a")
+	except IOError, e:
+	    self.log.error("export_userconf: can't open /dev/null: %s" % e)
+
+	cmd = "%s %s" % (self.userconfcmd, project)
+	conf = subprocess.call(cmd.split(" "),
+		stdout=cf, stderr=dev_null, close_fds=True)
+
+	self.auth.set_attribute(confid, "/%s" % str(confid))
+
+	return confid, confcert
+
+
+    def export_services(self, sreq, project, user):
+	exp = [ ]
+	state = { }
+	# XXX: Filthy shortcut here using http: so urlparse will give the right
+	# answers.
+	for s in sreq:
+	    sname = s.get('name', '')
+	    svis = s.get('visibility', '')
+	    if svis == 'export':
+		if sname in self.exports:
+		    outs = s.copy()
+		    if sname == 'SMB':
+			outs = s.copy()
+			outs['server'] = "http://fs:139" 
+			outs['fedAttr'] = [
+				{ 'attribute': 'SMBSHARE', 'value': 'USERS' },
+				{ 'attribute': 'SMBUSER', 'value': user },
+				{ 'attribute': 'SMBPROJ', 'value': project },
+			    ]
+		    elif sname == 'seer':
+			outs['server'] = "http://control:16606"
+		    elif sname == 'tmcd':
+			outs['server'] = "http://boss:7777"
+		    elif sname == 'userconfig':
+			if self.userconfdir and self.userconfcmd \
+				and self.userconfurl:
+			    cid, cert = self.export_userconf(project)
+			    outs['server'] = "%s/%s" % \
+				    (self.userconfurl, str(cid))
+			    outs['fedAttr'] = [
+				    { 'attribute': 'cert', 'value': cert },
+				]
+			    state['userconfig'] = unicode(cid)
+		    exp.append(outs)
+	return (exp, state)
+
+    def build_response(self, alloc_id, ap, services):
+        """
+        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,
+		'fedAttr': [
+		    { 'attribute': 'domain', 'value': self.domain } , 
+		    { 'attribute': 'project', 'value': 
+			ap['project'].get('name', {}).get('localname', "???") },
+		]
+            }
+        if len(self.attrs) > 0:
+            msg['fedAttr'].extend(
+                [ { 'attribute': x, 'value' : y } \
+                        for x,y in self.attrs.iteritems()])
+
+	if services:
+	    msg['service'] = services
+        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 in self.testbed:
+            # Request for this fedd
+            found, owners = self.lookup_access(req, fid)
+            # 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] = { }
+	    # The protoGENI certificate
+	    self.allocation[aid]['credentials'] = found
+	    # The list of owner FIDs
+	    self.allocation[aid]['owners'] = owners
+            self.write_state()
+            self.state_lock.release()
+            for o in owners:
+                self.auth.set_attribute(o, allocID)
+	    self.auth.set_attribute(allocID, allocID)
+
+	    try:
+		f = open("%s/%s.pem" % (self.certdir, aid), "w")
+		print >>f, alloc_cert
+		f.close()
+	    except IOError, e:
+		raise service_error(service_error.internal, 
+			"Can't open %s/%s : %s" % (self.certdir, aid, e))
+	    return { 'allocID': { 'fedid': allocID } }
+        else:
+            if self.allow_proxy:
+                resp = self.proxy_RequestAccess.call_service(dt, req,
+                            self.cert_file, self.cert_pwd,
+                            self.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 in 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")
+
+            self.log.debug("[access] deallocation requested for %s", aid)
+            if not self.auth.check_attribute(fid, auth_attr):
+                self.log.debug("[access] deallocation denied for %s", aid)
+                raise service_error(service_error.access, "Access Denied")
+
+	    self.state_lock.acquire()
+            if self.allocation.has_key(aid):
+                self.log.debug("Found allocation for %s" %aid)
+                del self.allocation[aid]
+                self.write_state()
+                self.state_lock.release()
+		# And remove the access cert
+		cf = "%s/%s.pem" % (self.certdir, aid)
+		self.log.debug("Removing %s" % cf)
+		os.remove(cf)
+                return { 'allocID': req['allocID'] } 
+            else:
+                self.state_lock.release()
+                raise service_error(service_error.req, "No such allocation")
+
+        else:
+            if self.allow_proxy:
+                resp = self.proxy_ReleaseAccess.call_service(dt, req,
+                            self.cert_file, self.cert_pwd,
+                            self.trusted_certs)
+                if resp.has_key('ReleaseAccessResponseBody'):
+                    return resp['ReleaseAccessResponseBody']
+                else:
+                    return None
+            else:
+                raise service_error(service_error.access,
+                        "Access proxying denied")
+
+    def import_store_info(self, cf, connInfo):
+	"""
+	Pull any import parameters in connInfo in.  We translate them either
+	into known member names or fedAddrs.
+	"""
+
+	for c in connInfo:
+	    for p in [ p for p in c.get('parameter', []) \
+		    if p.get('type', '') == 'input']:
+		name = p.get('name', None)
+		key = p.get('key', None)
+		store = p.get('store', None)
+
+		if name and key and store :
+		    req = { 'name': key, 'wait': True }
+		    r = self.call_GetValue(store, req, cf)
+		    r = r.get('GetValueResponseBody', None)
+		    if r :
+			if r.get('name', '') == key:
+			    v = r.get('value', None)
+			    if v is not None:
+				if name == 'peer':
+				    c['peer'] = v
+				else:
+				    if c.has_key('fedAttr'):
+					c['fedAttr'].append({
+					    'attribute': name, 'value': v})
+				    else:
+					c['fedAttr']= [{
+					    'attribute': name, 'value': v}]
+			    else:
+				raise service_error(service_error.internal, 
+					'None value exported for %s'  % key)
+			else:
+			    raise service_error(service_error.internal, 
+				    'Different name returned for %s: %s' \
+					    % (key, r.get('name','')))
+		    else:
+			raise service_error(service_error.internal, 
+			    'Badly formatted response: no GetValueResponseBody')
+		else:
+		    raise service_error(service_error.internal, 
+			'Bad Services missing info for import %s' % c)
+
+    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
+	    tmpdir, master, leid, connInfo, services):
+
+	def conninfo_to_dict(key, info):
+	    """
+	    Make a cpoy of the connection information about key, and flatten it
+	    into a single dict by parsing out any feddAttrs.
+	    """
+
+	    rv = None
+	    for i in info:
+		if key == i.get('portal', "") or \
+			key in [e.get('element', "") \
+			for e in i.get('member', [])]:
+		    rv = i.copy()
+		    break
+
+	    else:
+		return rv
+
+	    if 'fedAttr' in rv:
+		for a in rv['fedAttr']:
+		    attr = a.get('attribute', "")
+		    val = a.get('value', "")
+		    if attr and attr not in rv:
+			rv[attr] = val
+		del rv['fedAttr']
+	    return rv
+
+	# XXX: un hardcode this
+	def client_null(f, s):
+	    print >>f, "Service: %s" % s['name']
+
+	def client_smb(f, s):
+	    print >>f, "Service: %s" % s['name']
+	    smbshare = None
+	    smbuser = None
+	    smbproj = None
+	    for a in s.get('fedAttr', []):
+		if a.get('attribute', '') == 'SMBSHARE':
+		    smbshare = a.get('value', None)
+		elif a.get('attribute', '') == 'SMBUSER':
+		    smbuser = a.get('value', None)
+		elif a.get('attribute', '') == 'SMBPROJ':
+		    smbproj = a.get('value', None)
+
+	    if all((smbshare, smbuser, smbproj)):
+		print >>f, "SMBshare: %s" % smbshare
+		print >>f, "ProjectUser: %s" % smbuser
+		print >>f, "ProjectName: %s" % smbproj
+
+	client_service_out = {
+		'SMB': client_smb,
+		'tmcd': client_null,
+		'seer': client_null,
+		'userconfig': client_null,
+	    }
+	# XXX: end un hardcode this
+
+
+	seer_out = False
+	client_out = False
+	for e in [ e for e in topo.elements \
+		if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
+	    myname = e.name[0]
+	    type = e.get_attribute('portal_type')
+
+	    info = conninfo_to_dict(myname, connInfo)
+
+	    if not info:
+		raise service_error(service_error.req,
+			"No connectivity info for %s" % myname)
+
+	    peer = info.get('peer', "")
+	    ldomain = self.domain;
+
+	    mexp = info.get('masterexperiment',"")
+	    mproj, meid = mexp.split("/", 1)
+	    mdomain = info.get('masterdomain',"")
+	    muser = info.get('masteruser','root')
+	    smbshare = info.get('smbshare', 'USERS')
+	    ssh_port = info.get('ssh_port', '22')
+
+	    active = info.get('active', 'False')
+
+	    cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
+	    tunnelconfig = self.attrs.has_key('TunnelCfg')
+	    try:
+		f = open(cfn, "w")
+		if active == 'True':
+		    print >>f, "active: True"
+		    if type in ('control', 'both'):
+			for s in [s for s in services \
+				if s.get('name', "") in self.imports]:
+			    p = urlparse(s.get('server', 'http://localhost'))
+			    print >>f, 'port: remote:%s:%s:%s' % \
+				    (p.port, p.hostname, p.port)
+
+		if tunnelconfig:
+		    print >>f, "tunnelip: %s" % tunnelconfig
+		# XXX: send this an fedattr
+		#print >>f, "seercontrol: control.%s.%s%s" % \
+			#(meid.lower(), mproj.lower(), mdomain)
+		print >>f, "peer: %s" % peer.lower()
+		print >>f, "ssh_pubkey: /usr/local/federation/etc/%s" % \
+			pubkey_base
+		print >>f, "ssh_privkey: /usr/local/federation/etc/%s" % \
+			secretkey_base
+		print >>f, "ssh_port: %s" % ssh_port
+		f.close()
+	    except IOError, e:
+		raise service_error(service_error.internal,
+			"Can't write protal config %s: %s" % (cfn, e))
+	    
+	    # XXX: This little seer config file needs to go away.
+	    if not seer_out:
+		try:
+		    seerfn = "%s/seer.conf" % tmpdir
+		    f = open(seerfn, "w")
+		    if not master:
+			print >>f, "ControlNode: control.%s.%s%s" % \
+			    (meid.lower(), mproj.lower(), mdomain)
+		    print >>f, "ExperimentID: %s" % mexp
+		    f.close()
+		except IOError, e:
+		    raise service_error(service_error.internal, 
+			    "Can't write seer.conf: %s" %e)
+		seer_out = True
+
+	    if not client_out and type in ('control', 'both'):
+		try:
+		    f = open("%s/client.conf" % tmpdir, "w")
+		    print >>f, "ControlGateway: %s%s" % \
+			(myname.lower(), ldomain.lower())
+		    for s in services:
+			if s.get('name',"") in self.imports and \
+				s.get('visibility','') == 'import':
+			    client_service_out[s['name']](f, s)
+		    # Does seer need this?
+		    # print >>f, "ExperimentID: %s/%s" % (mproj, meid)
+		    f.close()
+		except IOError, e:
+		    raise service_error(service_error.internal,
+			    "Cannot write client.conf: %s" %s)
+		client_out = True
+
+
+    def generate_rspec(self, topo, softdir, master, connInfo):
+	t = topo.clone()
+
+	starts = { }
+
+	# The startcmds for master and slave testbeds
+	if master: 
+	    gate_cmd = self.attrs.get('MasterConnectorStartCmd', '/bin/true')
+	    node_cmd = self.attrs.get('MasterNodeStartCmd', 'bin/true')
+	else: 
+	    gate_cmd = self.attrs.get('SlaveConnectorStartCmd', '/bin/true')
+	    node_cmd = self.attrs.get('SlaveNodeStartCmd', 'bin/true')
+
+	# Weed out the things we aren't going to instantiate: Segments, portal
+	# substrates, and portal interfaces.  (The copy in the for loop allows
+	# us to delete from e.elements in side the for loop).  While we're
+	# touching all the elements, we also adjust paths from the original
+	# testbed to local testbed paths and put the federation commands and
+	# startcommands into a dict so we can start them manually later.
+	# ProtoGENI requires setup before the federation commands run, so we
+	# run them by hand after we've seeded configurations.
+	for e in [e for e in t.elements]:
+	    if isinstance(e, topdl.Segment):
+		t.elements.remove(e)
+	    # Fix software paths
+	    for s in getattr(e, 'software', []):
+		s.location = re.sub("^.*/", softdir, s.location)
+	    if isinstance(e, topdl.Computer):
+		if e.get_attribute('portal') and gate_cmd:
+		    # Portals never have a user-specified start command
+		    starts[e.name[0]] = gate_cmd
+		elif node_cmd:
+		    if e.get_attribute('startup'):
+			starts[e.name[0]] = "%s \\$USER '%s'" % \
+				(node_cmd, e.get_attribute('startup'))
+			e.remove_attribute('startup')
+		    else:
+			starts[e.name[0]] = node_cmd
+
+		# Remove portal interfaces 
+		e.interface = [i for i in e.interface \
+			if not i.get_attribute('portal')]
+
+	t.substrates = [ s.clone() for s in t.substrates ]
+	t.incorporate_elements()
+
+	# Customize the ns2 output for local portal commands and images
+	filters = []
+
+	# NB: these are extra commands issued for the node, not the startcmds
+	if master: cmd = self.attrs.get('MasterConnectorCmd', '')
+	else: cmd = self.attrs.get('SlaveConnectorCmd', '')
+
+	if cmd:
+	    filters.append(topdl.generate_portal_command_filter(cmd))
+
+	# Convert to rspec and return it
+	exp_rspec = topdl.topology_to_rspec(t, filters)
+
+	return exp_rspec
+
+    def StartSegment(self, req, fid):
+	def get_url(url, cf, destdir, fn=None):
+	    po = urlparse(url)
+	    if not fn:
+		fn = po.path.rpartition('/')[2]
+	    try:
+		conn = httplib.HTTPSConnection(po.hostname, port=po.port, 
+			cert_file=cf, key_file=cf)
+		conn.putrequest('GET', po.path)
+		conn.endheaders()
+		response = conn.getresponse()
+
+		lf = open("%s/%s" % (destdir, fn), "w")
+		buf = response.read(4096)
+		while buf:
+		    lf.write(buf)
+		    buf = response.read(4096)
+		lf.close()
+	    except IOError, e:
+		print e
+		raise service_error(service_error.internal,
+			"Error writing tempfile: %s" %e)
+	    except httplib.HTTPException, e:
+		print e
+		raise service_error(service_error.internal, 
+			"Error retrieving data: %s" % e)
+
+	configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
+
+	err = None  # Any service_error generated after tmpdir is created
+	rv = None   # Return value from segment creation
+
+	try:
+	    req = req['StartSegmentRequestBody']
+	except KeyError:
+	    raise service_error(service_error.req, "Badly formed request")
+
+	connInfo = req.get('connection', [])
+	services = req.get('service', [])
+	auth_attr = req['allocID']['fedid']
+	aid = "%s" % auth_attr
+	attrs = req.get('fedAttr', [])
+	if not self.auth.check_attribute(fid, auth_attr):
+	    raise service_error(service_error.access, "Access denied")
+
+
+	if req.has_key('segmentdescription') and \
+		req['segmentdescription'].has_key('topdldescription'):
+	    topo = \
+		topdl.Topology(**req['segmentdescription']['topdldescription'])
+	else:
+	    raise service_error(service_error.req, 
+		    "Request missing segmentdescription'")
+
+	master = req.get('master', False)
+
+	certfile = "%s/%s.pem" % (self.certdir, auth_attr)
+        try:
+            tmpdir = tempfile.mkdtemp(prefix="access-")
+	    softdir = "%s/software" % tmpdir
+        except IOError:
+            raise service_error(service_error.internal, "Cannot create tmp dir")
+
+	# Try block alllows us to clean up temporary files.
+	try:
+	    sw = set()
+	    for e in topo.elements:
+		for s in getattr(e, 'software', []):
+		    sw.add(s.location)
+	    if len(sw) > 0:
+		os.mkdir(softdir)
+	    for s in sw:
+		self.log.debug("Retrieving %s" % s)
+		get_url(s, certfile, softdir)
+
+	    for a in attrs:
+		if a['attribute'] in configs:
+		    get_url(a['value'], certfile, tmpdir)
+		if a['attribute'] == 'ssh_pubkey':
+		    pubkey_base = a['value'].rpartition('/')[2]
+		if a['attribute'] == 'ssh_secretkey':
+		    secretkey_base = a['value'].rpartition('/')[2]
+		if a['attribute'] == 'experiment_name':
+		    ename = a['value']
+
+	    # If the userconf service was imported, collect the configuration
+	    # data.
+	    for s in services:
+		if s.get("name", "") == 'userconfig' \
+			and s.get('visibility',"") == 'import':
+
+		    # Collect ther server and certificate info.
+		    u = s.get('server', None)
+		    for a in s.get('fedAttr', []):
+			if a.get('attribute',"") == 'cert':
+			    cert = a.get('value', None)
+			    break
+		    else:
+			cert = None
+
+		    if cert:
+			# Make a temporary certificate file for get_url.  The
+			# finally clause removes it whether something goes
+			# wrong (including an exception from get_url) or not.
+			try:
+			    tfos, tn = tempfile.mkstemp(suffix=".pem")
+			    tf = os.fdopen(tfos, 'w')
+			    print >>tf, cert
+			    tf.close()
+			    get_url(u, tn, tmpdir, "userconf")
+			except IOError, e:
+			    raise service_error(service.error.internal, 
+				    "Cannot create temp file for " + 
+				    "userconfig certificates: %s e")
+			finally:
+			    if tn: os.remove(tn)
+		    else:
+			raise service_error(service_error.req,
+				"No certificate for retreiving userconfig")
+		    break
+
+
+
+	    self.state_lock.acquire()
+	    if self.allocation.has_key(aid):
+		cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
+		self.allocation[aid]['experiment'] = ename
+		self.allocation[aid]['log'] = [ ]
+		# Create a logger that logs to the experiment's state object as
+		# well as to the main log file.
+		alloc_log = logging.getLogger('fedd.access.%s' % ename)
+		h = logging.StreamHandler(
+			list_log.list_log(self.allocation[aid]['log']))
+		# XXX: there should be a global one of these rather than
+		# repeating the code.
+		h.setFormatter(logging.Formatter(
+		    "%(asctime)s %(name)s %(message)s",
+			    '%d %b %y %H:%M:%S'))
+		alloc_log.addHandler(h)
+		self.write_state()
+	    else:
+		self.log.error("No allocation for %s!?" % aid)
+	    self.state_lock.release()
+
+	    # XXX: we really need to put the import and connection info
+	    # generation off longer.
+	    self.import_store_info(certfile, connInfo)
+	    self.generate_portal_configs(topo, pubkey_base, 
+		    secretkey_base, tmpdir, master, ename, connInfo, 
+		    services)
+	    rspec = self.generate_rspec(topo, "%s/%s/" \
+		    % (self.staging_dir, ename), master, connInfo)
+
+	    starter = self.start_segment(keyfile=ssh_key,
+		    debug=self.create_debug, log=alloc_log,
+		    ch_url = self.ch_url, sa_url=self.sa_url,
+		    cm_url=self.cm_url)
+	    rv = starter(self, aid, user, rspec, pubkey_base, secretkey_base,
+		    "%s/%s" % (self.staging_dir, ename), tmpdir, cf, cpw,
+		    certfile, topo, connInfo)
+	except service_error, e:
+	    err = e
+	except e:
+	    err = service_error(service_error.internal, str(e))
+
+        # Walk up tmpdir, deleting as we go
+	if self.cleanup:
+	    self.log.debug("[StartSegment]: removing %s" % tmpdir)
+	    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)
+	else:
+	    self.log.debug("[StartSegment]: not removing %s" % tmpdir)
+
+	if rv:
+	    # Grab the log (this is some anal locking, but better safe than
+	    # sorry)
+	    self.state_lock.acquire()
+	    logv = "".join(self.allocation[aid]['log'])
+	    self.state_lock.release()
+
+	    return { 'allocID': req['allocID'], 'allocationLog': logv }
+	elif err:
+	    raise service_error(service_error.federant,
+		    "Swapin failed: %s" % err)
+	else:
+	    raise service_error(service_error.federant, "Swapin failed")
+
+    def TerminateSegment(self, req, fid):
+	try:
+	    req = req['TerminateSegmentRequestBody']
+	except KeyError:
+	    raise service_error(service_error.req, "Badly formed request")
+
+	auth_attr = req['allocID']['fedid']
+	aid = "%s" % auth_attr
+	attrs = req.get('fedAttr', [])
+	if not self.auth.check_attribute(fid, auth_attr):
+	    raise service_error(service_error.access, "Access denied")
+
+	self.state_lock.acquire()
+	if self.allocation.has_key(aid):
+	    cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
+	    slice_cred = self.allocation[aid].get('slice_credential', None)
+	    ename = self.allocation[aid].get('experiment', None)
+	else:
+	    cf, user, ssh_key, cpw = (None, None, None, None)
+	    slice_cred = None
+	    ename = None
+	self.state_lock.release()
+
+	if ename:
+	    staging = "%s/%s" % ( self.staging_dir, ename)
+	else:
+	    self.log.warn("Can't find experiment name for %s" % aid)
+	    staging = None
+
+	stopper = self.stop_segment(keyfile=ssh_key, debug=self.create_debug,
+		ch_url = self.ch_url, sa_url=self.sa_url, cm_url=self.cm_url)
+	stopper(self, user, staging, slice_cred, cf, cpw)
+	return { 'allocID': req['allocID'] }
