Index: fedd/fedd_client.py
===================================================================
--- fedd/fedd_client.py	(revision 6ff0b91afedbb5c0b726a21c0c6cb3f24c66cebc)
+++ fedd/fedd_client.py	(revision 6ff0b91afedbb5c0b726a21c0c6cb3f24c66cebc)
@@ -0,0 +1,313 @@
+#!/usr/local/bin/python
+
+import sys
+import os
+import pwd
+
+from fedd_services import *
+
+from M2Crypto import SSL, X509
+import M2Crypto.httpslib
+
+from fedd_util import *
+
+from optparse import OptionParser, OptionValueError
+
+# Turn off the matching of hostname to certificate ID
+SSL.Connection.clientPostConnectionCheck = None
+
+class IDFormatException(RuntimeError): pass
+
+class access_method:
+    """Encapsulates an access method generically."""
+    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
+    default_type = type_ssh
+    def __init__(self, buf=None, type=None, file=None):
+	self.buf = buf
+
+	if type != None: self.type = type
+	else: self.type = access_method.default_type
+
+	if file != None:
+	    self.readfile(file)
+    
+    def readfile(self, file, type=None):
+	f = open(file, "r")
+	self.buf = f.read();
+	f.close()
+	if type == None:
+	    if self.type == None:
+		self.type = access_method.default_type
+	else:
+	    self.type = type;
+    
+class node_desc:
+    def __init__(self, image, hardware, count=1):
+	if getattr(image, "__iter__", None) == None:
+	    if image == None: self.image = [ ]
+	    else: self.image = [ image ]
+	else:
+	    self.image = image
+
+	if getattr(hardware, "__iter__", None) == None: 
+	    if hardware == None: self.hardware = [ ]
+	    else: self.hardware = [ hardware ]
+	else:
+	    self.hardware = hardware
+	if count != None: self.count = int(count)
+	else: self.count = 1
+
+class fedd_client_opts(OptionParser):
+    """Encapsulate option processing in this class, rather than in main"""
+    def __init__(self):
+	OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
+		version="0.1")
+
+	self.set_defaults(url="https://localhost:23235", anonymous=False,
+		use_fedid=False, debug=0)
+
+	self.add_option("-a","--anonymous", action="store_true",
+		dest="anonymous", help="Do not include a user in the request")
+	self.add_option("-c","--cert", action="store", dest="cert",
+		type="string", help="my certificate file")
+	self.add_option("-d", "--debug", action="count", dest="debug", 
+		help="Set debug.  Repeat for more information")
+	self.add_option("-f","--useFedid", action="store_true",
+		dest="use_fedid",
+		help="Use a fedid derived from my certificate as user identity")
+	self.add_option("-k", "--sshKey", action="callback", type="string", 
+		callback=add_ssh_key, callback_args=(access_keys,),
+		help="ssh key for access (can be supplied more than once")
+	self.add_option("-K", "--x509Key", action="callback", type="string", 
+		callback=add_x509_cert, callback_args=(access_keys,),
+		help="X509 certificate for access " + \
+			"(can be supplied more than once")
+	self.add_option("-l","--label", action="store", dest="label",
+		type="string", help="Label for output")
+	self.add_option("-n", "--node", action="callback", type="string", 
+		callback=add_node_desc, callback_args=(node_descs,),
+		help="Node description: image:hardware[:count]")
+	self.add_option("-p", "--project", action="store", dest="project", 
+		type="string",
+		help="Use a project request with this project name")
+	self.add_option("-t", "--testbed", action="store", dest="testbed",
+		type="string",
+		help="Testbed identifier (URI) to contact (required)")
+	self.add_option("-T","--trusted", action="store", dest="trusted",
+		type="string", help="Trusted certificates (required)")
+	self.add_option("-u", "--url", action="store", dest="url",
+		type="string",
+		help="URL to connect to (default %default)")
+	self.add_option("-U", "--username", action="store", dest="user",
+		type="string", help="Use this username instead of the uid")
+	self.add_option("--trace", action="store_const", dest="tracefile", 
+		const=sys.stderr, help="Print SOAP exchange to stderr")
+
+
+
+def print_ID(id, out=sys.stdout, prefix=None, no_nl=False):
+    """Print an ID element (which may have many types) to as a string
+    
+    If out is given, that is the output destination. If prefix is given that
+    string is prepended.  If no_nl is given, the newline is not printed.
+    """
+    # This comprehension selects all accessors from id of the form
+    # get_element_*, calls those in turn and returns a list of non-None ones.
+    # For valid ID elements that will contain exactly one element, which we
+    # print.  Anything else is an error.
+    names = [ getattr(id, method)() for method in dir(id) \
+		    if method.startswith("get_element_") \
+			and callable(getattr(id, method)) 
+			and getattr(id,method)() != None ]
+    if len(names) == 1:
+	if prefix == None: print >>out, names[0],
+	else: print >>out, "%s%s" % (prefix,names[0]),
+	if not no_nl: print >>out
+    else:
+	raise IDFormatException()
+
+
+def print_users(ul, out=sys.stdout):
+    """
+    Print a list of users, with "User: " prepended, one to a line on out.
+    
+    @param ul sequence of user elements
+    @param out output object.  Defaults to sys.stdout
+    """
+    if ul != None:
+	for u in ul:
+	    print_ID(u.get_element_userID(), out, "User: ")
+
+
+def print_response_as_testbed(resp, label, out=sys.stdout):
+    """Print the response as input to the splitter script"""
+    emulab_data = { 
+	    "Boss: ": "get_element_boss",
+	    "OpsNode: ": "get_element_ops",
+	    "Domain: ": "get_element_domain",
+	    "FileServer: ": "get_element_fileServer",
+	    "EventServer: ": "get_element_eventServer",
+	    }
+    if (label != None): print >> out, "[%s]" % label
+    e = resp.get_element_emulab()
+    if ( e != None):
+	p = e.get_element_project()
+	if ( p != None ):
+	    print_ID(p.get_element_name(), out, "Project: ")
+	    print_users(p.get_element_user(), out)
+	for k, m in emulab_data.iteritems():
+	    method = getattr(e, m);
+	    if method != None and callable(method):
+		print >> out, "%s%s" % (k, method())
+	for a in e.get_element_fedAttr():
+	    print >>out, "%s: %s" % \
+		    (a.get_element_attribute(), a.get_element_value())
+def add_ssh_key(option, opt_str, value, parser, access_keys):
+    try:
+	access_keys.append(access_method(file=value,
+	    type=access_method.type_ssh))
+    except IOError, (errno, strerror):
+	raise OptionValueError("Cannot generate sshPubkey from %s: %s (%d)" %
+		(value,strerror,errno))
+
+def add_x509_cert(option, opt_str, value, parser, access_keys):
+    try:
+	access_keys.append(access_method(file=value,
+	    type=access_method.type_x509))
+    except IOError, (errno, strerror):
+	raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
+		(value,strerror,errno))
+
+def add_node_desc(option, opt_str, value, parser, node_descs):
+    def none_if_zero(x):
+	if len(x) > 0: return x
+	else: return None
+
+    params = map(none_if_zero, value.split(":"));
+    
+    if len(params) < 4 and len(params) > 1:
+	node_descs.append(node_desc(*params))
+    else:
+	raise OptionValueError("Bad node description: %s" % value)
+
+def get_user_info(access_keys):
+    pw = pwd.getpwuid(os.getuid());
+    try_cert=None
+    user = None
+
+    if pw != None:
+	user = pw[0]
+	try_cert = "%s/.ssl/emulab.pem" % pw[5];
+	if not os.access(try_cert, os.R_OK):
+	    try_cert = None
+	if len(access_keys) == 0:
+	    for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
+		    "%s/.ssh/identity.pub"]:
+		try_key = k % pw[5];
+		if os.access(try_key, os.R_OK):
+		    access_keys.append(access_method(file=try_key,
+			type=access_method.type_ssh))
+		    break
+    return (user, try_cert)
+
+access_keys = []
+node_descs = []
+proj = None
+
+# Process the options using the customized option parser defined above
+(opts, args) = fedd_client_opts().parse_args()
+
+if opts.testbed == None:
+    parser.error("--testbed is required")
+
+if opts.trusted != None:
+    if ( not os.access(opts.trusted, os.R_OK) ) :
+	sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
+else:
+    parser.error("--trusted is required")
+
+if opts.debug > 0: opts.tracefile=sys.stderr
+
+(user, cert) = get_user_info(access_keys)
+
+if opts.user: user = opts.user
+
+if opts.cert != None: cert = opts.cert
+
+if cert == None:
+    sys.exit("No certificate given (--cert) or found")
+
+if os.access(cert, os.R_OK):
+    fid = fedid(file=cert)
+    if opts.use_fedid == True:
+	user = fid
+else:
+    sys.exit("Cannot read certificate (%s)" % cert)
+
+context = None
+while context == None:
+    try:
+	context = fedd_ssl_context(cert, opts.trusted)
+    except SSL.SSLError, e:
+	# Yes, doing this on message type is not ideal.  The string comes from
+	# OpenSSL, so check there is this stops working.
+	if e.message == "bad decrypt": 
+	    print >>sys.stderr, "Bad Passphrase given."
+	else: raise
+
+loc = feddServiceLocator();
+port = loc.getfeddPortType(opts.url,
+	transport=M2Crypto.httpslib.HTTPSConnection, 
+	transdict={ 'ssl_context' : context },
+	tracefile=opts.tracefile)
+
+req = RequestAccessRequestMessage()
+
+msg = {
+	'allocID': pack_id('test alloc'),
+	'destinationTestbed': pack_id(opts.testbed),
+	'access' : [ { a.type: a.buf } for a in access_keys ],
+	}
+
+if len(node_descs) > 0:
+    msg['resources'] = { 
+	    'node': [ 
+		{ 
+		    'image':  n.image ,
+		    'hardware':  n.hardware,
+		    'count': n.count,
+		} for n in node_descs],
+	    }
+
+if opts.project != None:
+    if not opts.anonymous and user != None:
+	msg['project'] = {
+		'name': pack_id(opts.project),
+		'user': [ { 'userID': pack_id(user) } ],
+		}
+    else:
+	msg['project'] = { 'name': pack_id(opts.project) }
+else:
+    if not opts.anonymous and user != None:
+	msg['user'] = [ { 'userID': pack_id(user) } ]
+    else:
+	msg['user'] = [];
+
+req.set_element_RequestAccessRequestBody(
+	pack_soap(req, "RequestAccessRequestBody", msg))
+
+
+try:
+    resp = port.RequestAccess(req)
+except ZSI.FaultException, e:
+    sys.exit("Fault: %s" % e)
+
+if (resp != None):
+    resp_body = resp.get_element_RequestAccessResponseBody()
+    if ( resp_body != None): 
+	try:
+	    print_response_as_testbed(resp_body, opts.label)
+	except RuntimeError, e:
+	    sys.exit("Bad response. %s" % e.messgae)
+    else: sys.exit("No body in resonpse!?")
+else: sys.exit("No response?!?")
