#!/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?!?")