Index: fedd/fedd_create.py
===================================================================
--- fedd/fedd_create.py	(revision 4d48e017adabc3e25838a0adb9256d18dc4dc5ce)
+++ fedd/fedd_create.py	(revision 4d48e017adabc3e25838a0adb9256d18dc4dc5ce)
@@ -0,0 +1,307 @@
+#!/usr/local/bin/python
+
+import sys
+import os
+import pwd
+
+from fedd_services import *
+
+from M2Crypto import SSL, X509
+from M2Crypto.m2xmlrpclib import SSL_Transport
+import M2Crypto.httpslib
+
+from xmlrpclib import ServerProxy, Error, dumps, loads
+from ZSI import SoapWriter
+from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
+from ZSI.wstools.Namespaces import SOAP
+from ZSI.fault import FaultType, Detail
+
+import xmlrpclib
+
+from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \
+	pack_id, unpack_id
+
+from optparse import OptionParser, OptionValueError
+
+import parse_detail
+
+# 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,
+		serialize_only=False, transport="soap", use_fedid=False,
+		debug=0, file=None, master=None)
+
+	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", "--file", dest="file", 
+		help="experiment description file")
+	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("-m", "--master", dest="master",
+		help="Master testbed in the federation")
+	self.add_option("-s", "--serializeOnly", action="store_true", 
+		dest="serialize_only",
+		help="Print the SOAP request that would be sent and exit")
+	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("-x","--transport", action="store", type="choice",
+		choices=("xmlrpc", "soap"),
+		help="Transport for request (xmlrpc|soap) (Default: %default)")
+	self.add_option("--trace", action="store_const", dest="tracefile", 
+		const=sys.stderr, help="Print SOAP exchange to stderr")
+
+def exit_with_fault(dict, out=sys.stderr):
+    """ Print an error message and exit.
+
+    The dictionary contains the FeddFaultBody elements."""
+    codestr = ""
+
+    if dict.has_key('errstr'):
+	codestr = "Error: %s" % dict['errstr']
+
+    if dict.has_key('code'):
+	if len(codestr) > 0 : 
+	    codestr += " (%d)" % dict['code']
+	else:
+	    codestr = "Error Code: %d" % dict['code']
+
+    print>>out, codestr
+    print>>out, "Description: %s" % dict['desc']
+    sys.exit(dict.get('code', 20))
+
+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 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 = []
+
+# Process the options using the customized option parser defined above
+parser = fedd_client_opts()
+
+(opts, args) = parser.parse_args()
+
+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)
+
+if opts.file:
+    exp_desc = ""
+    try:
+	f = open(opts.file, 'r')
+	for line in f:
+	    exp_desc += line
+	f.close()
+    except IOError:
+	sys.exit("Cannot read description file (%s)" %opts.file)
+else:
+    sys.exit("Must specify an experiment description (--file)")
+
+if not opts.master:
+    sys.exit("Must specify a master testbed (--master)")
+
+
+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 str(e) == "bad decrypt": 
+	    print >>sys.stderr, "Bad Passphrase given."
+	else: raise
+
+msg = {
+	'experimentdescription': exp_desc,
+	'master': opts.master,
+	'user' : [ {\
+		'userID': pack_id(user), \
+		'access': [ { a.type: a.buf } for a in access_keys]\
+		} ]
+	}
+
+if opts.debug > 1: print >>sys.stderr, msg
+
+if opts.transport == "soap":
+    loc = feddServiceLocator();
+    port = loc.getfeddPortType(opts.url,
+	    transport=M2Crypto.httpslib.HTTPSConnection, 
+	    transdict={ 'ssl_context' : context },
+	    tracefile=opts.tracefile)
+
+    req = CreateRequestMessage()
+
+    req.set_element_CreateRequestBody(
+	    pack_soap(req, "CreateRequestBody", msg))
+
+    if opts.serialize_only:
+	sw = SoapWriter()
+	sw.serialize(req)
+	print str(sw)
+	sys.exit(0)
+
+    try:
+	resp = port.Create(req)
+    except ZSI.ParseException, e:
+	sys.exit("Malformed response (XMLPRC?): %s" % e)
+    except ZSI.FaultException, e:
+	resp = e.fault.detail[0]
+
+    if resp:
+	if 'get_element_CreateResponseBody' in dir(resp): 
+	    resp_body = resp.get_element_CreateResponseBody()
+	    if ( resp_body != None): 
+		try:
+		    resp_dict = unpack_soap(resp_body)
+		    if opts.debug > 1: print >>sys.stderr, resp_dict
+		except RuntimeError, e:
+		    sys.exit("Bad response. %s" % e.message)
+	elif 'get_element_FeddFaultBody' in dir(resp): 
+	    resp_body = resp.get_element_FeddFaultBody()
+	    if resp_body != None: 
+		exit_with_fault(unpack_soap(resp_body))
+	else: sys.exit("No body in response!?")
+    else: sys.exit("No response?!?")
+elif opts.transport == "xmlrpc":
+    if opts.serialize_only:
+	ser = dumps((msg,))
+	print ser
+	sys.exit(0)
+
+    transport = SSL_Transport(context)
+    port = ServerProxy(opts.url, transport=transport)
+
+    try:
+	resp = port.RequestAccess({ 'CreateRequestBody': msg})
+	resp, method = loads(resp)
+	resp = resp[0]
+    except Error, e:
+	resp = { 'FeddFaultBody': \
+		{ 'errstr' : e.faultCode, 'desc' : e.faultString } }
+    if resp:
+	if resp.has_key('CreateResponseBody'): 
+	    try:
+		resp_dict = resp['CreateResponseBody']
+		if opts.debug > 1: print >>sys.stderr, resp_dict
+	    except RuntimeError, e:
+		sys.exit("Bad response. %s" % e.messgae)
+	elif resp.has_key('FeddFaultBody'):
+	    exit_with_fault(resp['FeddFaultBody'])
+	else: sys.exit("No body in response!?")
+
+    else: sys.exit("No response?!?")
