copy util dir into geni dir
authorTony Mack <tmack@cs.princeton.edu>
Tue, 27 Jan 2009 00:20:28 +0000 (00:20 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Tue, 27 Jan 2009 00:20:28 +0000 (00:20 +0000)
20 files changed:
geni/util/__init__.py [new file with mode: 0644]
geni/util/cert.py [new file with mode: 0644]
geni/util/config.py [new file with mode: 0644]
geni/util/credential.py [new file with mode: 0644]
geni/util/excep.py [new file with mode: 0644]
geni/util/geni_config [new file with mode: 0644]
geni/util/geniclient.py [new file with mode: 0644]
geni/util/geniserver.py [new file with mode: 0644]
geni/util/genitable.py [new file with mode: 0644]
geni/util/geniticket.py [new file with mode: 0644]
geni/util/gid.py [new file with mode: 0644]
geni/util/hierarchy.py [new file with mode: 0644]
geni/util/misc.py [new file with mode: 0644]
geni/util/record.py [new file with mode: 0644]
geni/util/remoteshell.py [new file with mode: 0644]
geni/util/report.py [new file with mode: 0644]
geni/util/rights.py [new file with mode: 0644]
geni/util/rspec.py [new file with mode: 0644]
geni/util/trustedroot.py [new file with mode: 0644]
geni/util/util.py [new file with mode: 0644]

diff --git a/geni/util/__init__.py b/geni/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/geni/util/cert.py b/geni/util/cert.py
new file mode 100644 (file)
index 0000000..5d4796e
--- /dev/null
@@ -0,0 +1,471 @@
+##
+# Geniwrapper uses two crypto libraries: pyOpenSSL and M2Crypto to implement
+# the necessary crypto functionality. Ideally just one of these libraries
+# would be used, but unfortunately each of these libraries is independently
+# lacking. The pyOpenSSL library is missing many necessary functions, and
+# the M2Crypto library has crashed inside of some of the functions. The
+# design decision is to use pyOpenSSL whenever possible as it seems more
+# stable, and only use M2Crypto for those functions that are not possible
+# in pyOpenSSL.
+#
+# This module exports two classes: Keypair and Certificate.
+##
+
+import os
+import tempfile
+from OpenSSL import crypto
+import M2Crypto
+from M2Crypto import X509
+from M2Crypto import EVP
+
+from excep import *
+
+##
+# Public-private key pairs are implemented by the Keypair class.
+# A Keypair object may represent both a public and private key pair, or it
+# may represent only a public key (this usage is consistent with OpenSSL).
+
+class Keypair:
+   key = None       # public/private keypair
+   m2key = None     # public key (m2crypto format)
+
+   ##
+   # Creates a Keypair object
+   # @param create If create==True, creates a new public/private key and
+   #     stores it in the object
+   # @param string If string!=None, load the keypair from the string (PEM)
+   # @param filename If filename!=None, load the keypair from the file
+
+   def __init__(self, create=False, string=None, filename=None):
+      if create:
+         self.create()
+      if string:
+         self.load_from_string(string)
+      if filename:
+         self.load_from_file(filename)
+
+   ##
+   # Create a RSA public/private key pair and store it inside the keypair object
+
+   def create(self):\r
+      self.key = crypto.PKey()
+      self.key.generate_key(crypto.TYPE_RSA, 1024)
+
+   ##
+   # Save the private key to a file
+   # @param filename name of file to store the keypair in
+
+   def save_to_file(self, filename):
+      open(filename, 'w').write(self.as_pem())
+
+   ##
+   # Load the private key from a file. Implicity the private key includes the public key.
+
+   def load_from_file(self, filename):
+      buffer = open(filename, 'r').read()
+      self.load_from_string(buffer)
+
+   ##
+   # Load the private key from a string. Implicitly the private key includes the public key.
+
+   def load_from_string(self, string):
+      self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
+      self.m2key = M2Crypto.EVP.load_key_string(string)
+
+   ##
+   #  Load the public key from a string. No private key is loaded. 
+
+   def load_pubkey_from_file(self, filename):
+      # load the m2 public key
+      m2rsakey = M2Crypto.RSA.load_pub_key(filename)
+      self.m2key = M2Crypto.EVP.PKey()
+      self.m2key.assign_rsa(m2rsakey)
+
+      # create an m2 x509 cert
+      m2name = M2Crypto.X509.X509_Name()
+      m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0)
+      m2x509 = M2Crypto.X509.X509()
+      m2x509.set_pubkey(self.m2key)
+      m2x509.set_serial_number(0)
+      m2x509.set_issuer_name(m2name)
+      m2x509.set_subject_name(m2name)
+      ASN1 = M2Crypto.ASN1.ASN1_UTCTIME()
+      ASN1.set_time(500)
+      m2x509.set_not_before(ASN1)
+      m2x509.set_not_after(ASN1)
+      junk_key = Keypair(create=True)
+      m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1")
+
+      # convert the m2 x509 cert to a pyopenssl x509
+      m2pem = m2x509.as_pem()
+      pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem)
+
+      # get the pyopenssl pkey from the pyopenssl x509
+      self.key = pyx509.get_pubkey()
+
+   ##
+   # Load the public key from a string. No private key is loaded.
+
+   def load_pubkey_from_string(self, string):
+      (f, fn) = tempfile.mkstemp()
+      os.write(f, string)
+      os.close(f)
+      self.load_pubkey_from_file(fn)
+      os.remove(fn)
+
+   ##
+   # Return the private key in PEM format.
+
+   def as_pem(self):
+      return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key)
+
+   ##
+   # Return an OpenSSL pkey object
+
+   def get_m2_pkey(self):
+      if not self.m2key:
+         self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
+      return self.m2key
+
+   ##
+   # Given another Keypair object, return TRUE if the two keys are the same.
+
+   def get_openssl_pkey(self):
+      return self.key
+
+   def is_same(self, pkey):
+      return self.as_pem() == pkey.as_pem()
+
+##
+# The certificate class implements a general purpose X509 certificate, making
+# use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds
+# several addition features, such as the ability to maintain a chain of
+# parent certificates, and storage of application-specific data.
+#
+# Certificates include the ability to maintain a chain of parents. Each\r
+# certificate includes a pointer to it's parent certificate. When loaded\r
+# from a file or a string, the parent chain will be automatically loaded.\r
+# When saving a certificate to a file or a string, the caller can choose\r
+# whether to save the parent certificates as well.\r
+
+class Certificate:
+   digest = "md5"
+
+   data = None
+   cert = None
+   issuerKey = None
+   issuerSubject = None
+   parent = None
+
+   ##
+   # Create a certificate object.
+   #\r
+   # @param create If create==True, then also create a blank X509 certificate.\r
+   # @param subject If subject!=None, then create a blank certificate and set\r
+   #     it's subject name.\r
+   # @param string If string!=None, load the certficate from the string.\r
+   # @param filename If filename!=None, load the certficiate from the file.\r
+
+   def __init__(self, create=False, subject=None, string=None, filename=None):
+       if create or subject:
+           self.create()
+       if subject:
+           self.set_subject(subject)
+       if string:
+           self.load_from_string(string)
+       if filename:
+           self.load_from_file(filename)
+
+   ##
+   # Create a blank X509 certificate and store it in this object.
+
+   def create(self):
+       self.cert = crypto.X509()
+       self.cert.set_serial_number(1)
+       self.cert.gmtime_adj_notBefore(0)
+       self.cert.gmtime_adj_notAfter(60*60*24*365*5) # five years
+
+   ##
+   # Given a pyOpenSSL X509 object, store that object inside of this
+   # certificate object.
+
+   def load_from_pyopenssl_x509(self, x509):
+       self.cert = x509
+
+   ##
+   # Load the certificate from a string
+
+   def load_from_string(self, string):
+       # if it is a chain of multiple certs, then split off the first one and
+       # load it
+       parts = string.split("-----parent-----", 1)
+       self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0])
+
+       # if there are more certs, then create a parent and let the parent load
+       # itself from the remainder of the string
+       if len(parts) > 1:
+           self.parent = self.__class__()
+           self.parent.load_from_string(parts[1])
+
+   ##
+   # Load the certificate from a file
+
+   def load_from_file(self, filename):
+       file = open(filename)
+       string = file.read()
+       self.load_from_string(string)
+
+   ##
+   # Save the certificate to a string.
+   #
+   # @param save_parents If save_parents==True, then also save the parent certificates.
+
+   def save_to_string(self, save_parents=False):
+       string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
+       if save_parents and self.parent:
+          string = string + "-----parent-----" + self.parent.save_to_string(save_parents)
+       return string
+
+   ##
+   # Save the certificate to a file.
+   # @param save_parents If save_parents==True, then also save the parent certificates.
+
+   def save_to_file(self, filename, save_parents=False):
+       string = self.save_to_string(save_parents=save_parents)
+       open(filename, 'w').write(string)
+
+   ##
+   # Sets the issuer private key and name
+   # @param key Keypair object containing the private key of the issuer
+   # @param subject String containing the name of the issuer
+   # @param cert (optional) Certificate object containing the name of the issuer
+
+   def set_issuer(self, key, subject=None, cert=None):
+       self.issuerKey = key
+       if subject:
+          # it's a mistake to use subject and cert params at the same time
+          assert(not cert)
+          if isinstance(subject, dict) or isinstance(subject, str):
+             req = crypto.X509Req()
+             reqSubject = req.get_subject()
+             if (isinstance(subject, dict)):
+                for key in reqSubject.keys():
+                    setattr(reqSubject, key, name[key])
+             else:
+                setattr(reqSubject, "CN", subject)
+             subject = reqSubject
+             # subject is not valid once req is out of scope, so save req
+             self.issuerReq = req
+       if cert:
+          # if a cert was supplied, then get the subject from the cert
+          subject = cert.cert.get_issuer()
+       assert(subject)
+       self.issuerSubject = subject
+
+   ##
+   # Get the issuer name
+
+   def get_issuer(self, which="CN"):
+       x = self.cert.get_issuer()
+       return getattr(x, which)
+
+   ##
+   # Set the subject name of the certificate
+
+   def set_subject(self, name):
+       req = crypto.X509Req()
+       subj = req.get_subject()
+       if (isinstance(name, dict)):
+           for key in name.keys():
+               setattr(subj, key, name[key])
+       else:
+           setattr(subj, "CN", name)
+       self.cert.set_subject(subj)
+   ##
+   # Get the subject name of the certificate
+
+   def get_subject(self, which="CN"):
+       x = self.cert.get_subject()
+       return getattr(x, which)
+
+   ##
+   # Get the public key of the certificate.
+   #
+   # @param key Keypair object containing the public key
+
+   def set_pubkey(self, key):
+       assert(isinstance(key, Keypair))
+       self.cert.set_pubkey(key.get_openssl_pkey())
+
+   ##
+   # Get the public key of the certificate.
+   # It is returned in the form of a Keypair object.
+
+   def get_pubkey(self):
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       pkey = Keypair()
+       pkey.key = self.cert.get_pubkey()
+       pkey.m2key = m2x509.get_pubkey()
+       return pkey
+
+   ##
+   # Add an X509 extension to the certificate. Add_extension can only be called
+   # once for a particular extension name, due to limitations in the underlying
+   # library.
+   #
+   # @param name string containing name of extension
+   # @param value string containing value of the extension
+
+   def add_extension(self, name, critical, value):
+       ext = crypto.X509Extension (name, critical, value)
+       self.cert.add_extensions([ext])
+
+   ##
+   # Get an X509 extension from the certificate
+
+   def get_extension(self, name):
+       # pyOpenSSL does not have a way to get extensions
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       value = m2x509.get_ext(name).get_value()
+       return value
+
+   ##
+   # Set_data is a wrapper around add_extension. It stores the parameter str in
+   # the X509 subject_alt_name extension. Set_data can only be called once, due
+   # to limitations in the underlying library.
+
+   def set_data(self, str):
+       # pyOpenSSL only allows us to add extensions, so if we try to set the
+       # same extension more than once, it will not work
+       if self.data != None:
+          raise "cannot set subjectAltName more than once"
+       self.data = str
+       self.add_extension("subjectAltName", 0, "URI:http://" + str)
+
+   ##
+   # Return the data string that was previously set with set_data
+
+   def get_data(self):
+       if self.data:
+           return self.data
+
+       try:
+           uri = self.get_extension("subjectAltName")
+       except LookupError:
+           self.data = None
+           return self.data
+
+       if not uri.startswith("URI:http://"):
+           raise "bad encoding in subjectAltName"
+       self.data = uri[11:]
+       return self.data
+
+   ##
+   # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer().
+
+   def sign(self):
+       assert self.cert != None
+       assert self.issuerSubject != None
+       assert self.issuerKey != None
+       self.cert.set_issuer(self.issuerSubject)
+       self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest)
+
+    ##
+    # Verify the authenticity of a certificate.
+    # @param pkey is a Keypair object representing a public key. If Pkey
+    #     did not sign the certificate, then an exception will be thrown.
+\r
+   def verify(self, pkey):
+       # pyOpenSSL does not have a way to verify signatures
+       m2x509 = X509.load_cert_string(self.save_to_string())
+       m2pkey = pkey.get_m2_pkey()
+       # verify it
+       return m2x509.verify(m2pkey)
+
+       # XXX alternatively, if openssl has been patched, do the much simpler:
+       # try:
+       #   self.cert.verify(pkey.get_openssl_key())
+       #   return 1
+       # except:
+       #   return 0
+
+   ##
+   # Return True if pkey is identical to the public key that is contained in the certificate.
+   # @param pkey Keypair object
+
+   def is_pubkey(self, pkey):
+       return self.get_pubkey().is_same(pkey)
+
+   ##
+   # Given a certificate cert, verify that this certificate was signed by the
+   # public key contained in cert. Throw an exception otherwise.
+   #
+   # @param cert certificate object
+
+   def is_signed_by_cert(self, cert):
+       k = cert.get_pubkey()
+       result = self.verify(k)
+       return result
+
+   ##
+   # Set the parent certficiate.
+   #
+   # @param p certificate object.
+
+   def set_parent(self, p):
+        self.parent = p
+
+   ##
+   # Return the certificate object of the parent of this certificate.
+
+   def get_parent(self):
+        return self.parent
+
+   ##
+   # Verification examines a chain of certificates to ensure that each parent
+   # signs the child, and that some certificate in the chain is signed by a
+   # trusted certificate.
+   #
+   # Verification is a basic recursion: <pre>
+   #     if this_certificate was signed by trusted_certs:\r
+   #         return\r
+   #     else\r
+   #         return verify_chain(parent, trusted_certs)\r
+   # </pre>\r
+   #\r
+   # At each recursion, the parent is tested to ensure that it did sign the
+   # child. If a parent did not sign a child, then an exception is thrown. If
+   # the bottom of the recursion is reached and the certificate does not match
+   # a trusted root, then an exception is thrown.
+   #
+   # @param Trusted_certs is a list of certificates that are trusted.\r
+   #
+
+   def verify_chain(self, trusted_certs = None):
+        # Verify a chain of certificates. Each certificate must be signed by
+        # the public key contained in it's parent. The chain is recursed
+        # until a certificate is found that is signed by a trusted root.
+
+        # TODO: verify expiration time
+
+        # if this cert is signed by a trusted_cert, then we are set
+        for trusted_cert in trusted_certs:
+            # TODO: verify expiration of trusted_cert ?
+            if self.is_signed_by_cert(trusted_cert):
+                #print self.get_subject(), "is signed by a root"
+                return
+
+        # if there is no parent, then no way to verify the chain
+        if not self.parent:
+            #print self.get_subject(), "has no parent"
+            raise CertMissingParent(self.get_subject())
+
+        # if it wasn't signed by the parent...
+        if not self.is_signed_by_cert(self.parent):
+            #print self.get_subject(), "is not signed by parent"
+            return CertNotSignedByParent(self.get_subject())
+
+        # if the parent isn't verified...
+        self.parent.verify_chain(trusted_certs)
+
+        return
diff --git a/geni/util/config.py b/geni/util/config.py
new file mode 100644 (file)
index 0000000..36eacb1
--- /dev/null
@@ -0,0 +1,89 @@
+##
+# Geniwrapper Configuration Info
+#
+# This module holds configuration parameters for geniwrapper. There are two
+# main pieces of information that are used: the database connection and
+# the PLCAPI connection
+##
+
+##
+# Geniwrapper uses a MYSQL database to store records. This database may be
+# co-located with the PLC database, or it may be a separate database. The
+# following parameters define the connection to the database.
+#
+# Note that Geniwrapper does not access any of the PLC databases directly via
+# a mysql connection; All PLC databases are accessed via PLCAPI.
+
+import os
+import sys
+
+# If we have been checked out into a directory at the same
+# level as myplc, where plc_config.py lives. If we are in a
+# MyPLC environment, plc_config.py has already been installed
+# in site-packages.
+myplc = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + \
+        os.sep + "myplc"
+
+class Config:
+    """
+    Parse the bash/Python/PHP version of the configuration file. Very
+    fast but no type conversions.
+    """
+
+    def __init__(self, file = "/usr/share/geniwrapper/util/geni_config"):
+        # Load plc_config
+
+       loaded = False
+       path = os.path.dirname(os.path.abspath(__file__))
+       filename = file.split(os.sep)[-1]
+       alt_file = path + os.sep + filename
+       myplc_file = myplc + os.sep + filename
+       files = [file, alt_file, myplc_file]
+
+       for file in files:
+           try: 
+               execfile(file, self.__dict__)
+               loaded = True
+           except:
+               pass
+
+       if not loaded:
+           raise Exception, "Could not find config in " + ", ".join(files)
+
+
+    def load(self, file):
+       try:
+           execfile(file, self.__dict__)
+       except:
+           raise Exception, "Could not find config in " + file
+
+plcConfig = Config("/etc/planetlab/plc_config")
+
+def get_default_dbinfo():
+    dbinfo={ 'dbname' : plcConfig.PLC_DB_NAME,
+    'address' : plcConfig.PLC_DB_HOST,
+    'port' : plcConfig.PLC_DB_PORT,
+    'user' : plcConfig.PLC_DB_USER,
+    'password' : plcConfig.PLC_DB_PASSWORD
+       }
+
+    return dbinfo
+
+##
+# Geniwrapper uses a PLCAPI connection to perform operations on the registry,
+# such as creating and deleting slices. This connection requires an account
+# on the PLC server with full administrator access.
+#
+# The Url parameter controls whether the connection uses PLCAPI directly (i.e.
+# Geniwrapper is located on the same machine as PLC), or uses a XMLRPC connection
+# to the PLC machine. If you wish to use the API directly, then remove the Url
+# field from the dictionary. 
+
+def get_pl_auth():
+    pl_auth = {'Username': plcConfig.PLC_API_MAINTENANCE_USER,
+    'AuthMethod': 'capability',
+    'AuthString':  plcConfig.PLC_MAINTENANCE_PASSWORD,
+    "Url": 'https://%s:%s%s' %(plcConfig.PLC_API_HOST, plcConfig.PLC_API_PORT, plcConfig.PLC_API_PATH)
+    }
+
+    return pl_auth
diff --git a/geni/util/credential.py b/geni/util/credential.py
new file mode 100644 (file)
index 0000000..697adf0
--- /dev/null
@@ -0,0 +1,236 @@
+##
+# Implements Geni Credentials
+#
+# Credentials are layered on top of certificates, and are essentially a
+# certificate that stores a tuple of parameters.
+##
+
+from cert import *
+from rights import *
+from gid import *
+import xmlrpclib
+
+##
+# Credential is a tuple:
+#     (GIDCaller, GIDObject, LifeTime, Privileges, Delegate)
+#
+# These fields are encoded using xmlrpc into the subjectAltName field of the
+# x509 certificate. Note: Call encode() once the fields have been filled in
+# to perform this encoding.
+
+class Credential(Certificate):
+    gidCaller = None
+    gidObject = None
+    lifeTime = None
+    privileges = None
+    delegate = False
+
+    ##
+    # Create a Credential object
+    #
+    # @param create If true, create a blank x509 certificate
+    # @param subject If subject!=None, create an x509 cert with the subject name
+    # @param string If string!=None, load the credential from the string
+    # @param filename If filename!=None, load the credential from the file
+
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        Certificate.__init__(self, create, subject, string, filename)
+
+    ##
+    # set the GID of the caller
+    #
+    # @param gid GID object of the caller
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    ##
+    # set the GID of the object
+    #
+    # @param gid GID object of the object
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    ##
+    # get the GID of the object
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    ##
+    # set the lifetime of this credential
+    #
+    # @param lifetime lifetime of credential
+
+    def set_lifetime(self, lifeTime):
+        self.lifeTime = lifeTime
+
+    ##
+    # get the lifetime of the credential
+
+    def get_lifetime(self):
+        if not self.lifeTime:
+            self.decode()
+        return self.lifeTime
+
+    ##
+    # set the delegate bit
+    #
+    # @param delegate boolean (True or False)
+
+    def set_delegate(self, delegate):
+        self.delegate = delegate
+
+    ##
+    # get the delegate bit
+
+    def get_delegate(self):
+        if not self.delegate:
+            self.decode()
+        return self.delegate
+
+    ##
+    # set the privileges
+    #
+    # @param privs either a comma-separated list of privileges of a RightList object
+
+    def set_privileges(self, privs):
+        if isinstance(privs, str):
+            self.privileges = RightList(string = privs)
+        else:
+            self.privileges = privs
+
+    ##
+    # return the privileges as a RightList object
+
+    def get_privileges(self):
+        if not self.privileges:
+            self.decode()
+        return self.privileges
+
+    ##
+    # determine whether the credential allows a particular operation to be
+    # performed
+    #
+    # @param op_name string specifying name of operation ("lookup", "update", etc)
+
+    def can_perform(self, op_name):
+        rights = self.get_privileges()
+        if not rights:
+            return False
+        return rights.can_perform(op_name)
+
+    ##
+    # Encode the attributes of the credential into a string and store that
+    # string in the alt-subject-name field of the X509 object. This should be
+    # done immediately before signing the credential.
+
+    def encode(self):
+        dict = {"gidCaller": None,
+                "gidObject": None,
+                "lifeTime": self.lifeTime,
+                "privileges": None,
+                "delegate": self.delegate}
+        if self.gidCaller:
+            dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True)
+        if self.gidObject:
+            dict["gidObject"] = self.gidObject.save_to_string(save_parents=True)
+        if self.privileges:
+            dict["privileges"] = self.privileges.save_to_string()
+        str = xmlrpclib.dumps((dict,), allow_none=True)
+        self.set_data(str)
+
+    ##
+    # Retrieve the attributes of the credential from the alt-subject-name field
+    # of the X509 certificate. This is automatically done by the various
+    # get_* methods of this class and should not need to be called explicitly.
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_data())[0][0]
+        else:
+            dict = {}
+
+        self.lifeTime = dict.get("lifeTime", None)
+        self.delegate = dict.get("delegate", None)
+
+        privStr = dict.get("privileges", None)
+        if privStr:
+            self.privileges = RightList(string = privStr)
+        else:
+            self.privileges = None
+
+        gidCallerStr = dict.get("gidCaller", None)
+        if gidCallerStr:
+            self.gidCaller = GID(string=gidCallerStr)
+        else:
+            self.gidCaller = None
+
+        gidObjectStr = dict.get("gidObject", None)
+        if gidObjectStr:
+            self.gidObject = GID(string=gidObjectStr)
+        else:
+            self.gidObject = None
+
+    ##
+    # Verify that a chain of credentials is valid (see cert.py:verify). In
+    # addition to the checks for ordinary certificates, verification also
+    # ensures that the delegate bit was set by each parent in the chain. If
+    # a delegate bit was not set, then an exception is thrown.
+    #
+    # Each credential must be a subset of the rights of the parent.
+
+    def verify_chain(self, trusted_certs = None):
+        # do the normal certificate verification stuff
+        Certificate.verify_chain(self, trusted_certs)
+
+        if self.parent:
+            # make sure the parent delegated rights to the child
+            if not self.parent.get_delegate():
+                raise MissingDelegateBit(self.parent.get_subject())
+
+            # make sure the rights given to the child are a subset of the
+            # parents rights
+            if not self.parent.get_privileges().is_superset(self.get_privileges()):
+                raise ChildRightsNotSubsetOfParent(self.get_subject())
+
+        return
+
+    ##
+    # Dump the contents of a credential to stdout in human-readable format
+    #
+    # @param dump_parents If true, also dump the parent certificates
+
+    def dump(self, dump_parents=False):
+        print "CREDENTIAL", self.get_subject()
+
+        print "      privs:", self.get_privileges().save_to_string()
+
+        print "  gidCaller:"
+        gidCaller = self.get_gid_caller()
+        if gidCaller:
+            gidCaller.dump(8, dump_parents)
+
+        print "  gidObject:"
+        gidObject = self.get_gid_object()
+        if gidObject:
+            gidObject.dump(8, dump_parents)
+
+        print "   delegate:", self.get_delegate()
+
+        if self.parent and dump_parents:
+           print "PARENT",
+           self.parent.dump(dump_parents)
+
diff --git a/geni/util/excep.py b/geni/util/excep.py
new file mode 100644 (file)
index 0000000..5a6c99f
--- /dev/null
@@ -0,0 +1,129 @@
+
+class MalformedHrnException(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class TreeException(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class NonexistingRecord(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class ExistingRecord(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+        
+class NonexistingCredType(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class NonexistingFile(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class InvalidRPCParams(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+# SMBAKER exceptions follow
+
+class ConnectionKeyGIDMismatch(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class MissingCallerGID(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class RecordNotFound(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class UnknownGeniType(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class MissingAuthority(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class PlanetLabRecordDoesNotExist(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class PermissionError(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class InsufficientRights(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class MissingDelegateBit(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class ChildRightsNotSubsetOfParent(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class CertMissingParent(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class CertNotSignedByParent(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class GidInvalidParentHrn(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+class SliverDoesNotExist(Exception):
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
diff --git a/geni/util/geni_config b/geni/util/geni_config
new file mode 100644 (file)
index 0000000..e70fe4a
--- /dev/null
@@ -0,0 +1,80 @@
+# Directory where Geni intefaces are installed
+GENI_BASE_DIR="/usr/share/geniwrapper/" 
+
+# HRN
+# Human readable name for the interfaces
+GENI_INTERFACE_HRN="planetlab.us"   
+
+
+## Aggregate Configuration
+#
+# Enable aggregate inteface
+# Enable the aggregate inteface.   
+GENI_AGGREGATE_ENABLED=1
+
+# Hostname
+# The fully qualified hostname of the aggregate server  
+GENI_AGGREGATE_HOSTNAME="localhost"
+
+# IP
+# Geni aggregate ip address   
+GENI_AGGREGATE_IP="127.0.0.1"
+
+# Port
+# Geni aggregate server port
+GENI_AGGREGATE_PORT=12346
+
+
+# Registry Configuration
+#
+# Enabled
+# Enable the registry interface 
+GENI_REGISTRY_ENABLE=1
+
+# Hostname
+# The fully qualified hostname of the registry server
+GENI_REGISTRY_HOSTNAME="localhost"
+
+# IP
+# Geni registry ip address 
+GENI_REGISTRY_IP="127.0.0.1"
+
+# Port
+# Geni registry port
+GENI_REGISTRY_PORT=12345
+
+# Slice Manager Configuration
+#
+# Enabled
+# Enable the slice manager  
+GENI_SM_ENABLED=1 
+
+
+# PLC Interace Access Configuration
+#
+# PLC user
+# Valid plc account geni interfaces will use for plc requests   
+
+GENI_PLC_USER='root@localhost.localdomain'
+
+# Passowrd
+# Password for user account
+GENI_PLC_PASSWORD='root'
+
+# Hostname
+# Fully qualified hostname of PLC interface
+GENI_PLC_HOST='localhost'
+
+# Port
+# Port of PLC interface
+GENI_PLC_PORT=443
+
+# API Path
+# Path of PLC api interface 
+GENI_PLC_API_PATH='PLCAPI'
+
+# PLC Shell PATH
+# Path of PLC shell. This only applies if Geni interfaces and PLC interface are running on the same 
+# machine
+GENI_PLC_SHELL_PATH='/usr/share/plc_api'
+    
diff --git a/geni/util/geniclient.py b/geni/util/geniclient.py
new file mode 100644 (file)
index 0000000..f3013df
--- /dev/null
@@ -0,0 +1,333 @@
+##
+# This module implements the client-side of the Geni API. Stubs are provided
+# that convert the supplied parameters to the necessary format and send them
+# via XMLRPC to a Geni Server.
+#
+# TODO: Investigate ways to combine this with existing PLC API?
+##
+
+import xmlrpclib
+
+from gid import *
+from credential import *
+from record import *
+from geniticket import *
+
+##
+# ServerException, ExceptionUnmarshaller
+#
+# Used to convert server exception strings back to an exception.
+#    from usenet, Raghuram Devarakonda
+
+class ServerException(Exception):
+    pass
+
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+    def close(self):\r
+        try:\r
+            return xmlrpclib.Unmarshaller.close(self)\r
+        except xmlrpclib.Fault, e:\r
+            raise ServerException(e.faultString)
+
+##
+# GeniTransport
+#
+# A transport for XMLRPC that works on top of HTTPS
+
+class GeniTransport(xmlrpclib.Transport):
+    key_file = None
+    cert_file = None
+    def make_connection(self, host):\r
+        # create a HTTPS connection object from a host descriptor\r
+        # host may be a string, or a (host, x509-dict) tuple\r
+        import httplib\r
+        host, extra_headers, x509 = self.get_host_info(host)\r
+        try:\r
+            HTTPS = httplib.HTTPS()\r
+        except AttributeError:\r
+            raise NotImplementedError(\r
+                "your version of httplib doesn't support HTTPS"\r
+                )\r
+        else:\r
+            return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))\r
+\r
+    def getparser(self):\r
+        unmarshaller = ExceptionUnmarshaller()\r
+        parser = xmlrpclib.ExpatParser(unmarshaller)\r
+        return parser, unmarshaller\r
+\r
+##\r
+# The GeniClient class provides stubs for executing Geni operations. A given\r
+# client object connects to one server. To connect to multiple servers, create\r
+# multiple GeniClient objects.\r
+#\r
+# The Geni protocol uses an HTTPS connection, and the client's side of the\r
+# connection uses his private key. Generally, this private key must match the\r
+# public key that is containing in the GID that the client is providing for\r
+# those functions that take a GID.\r
+\r
+class GeniClient():
+    ##
+    # Create a new GeniClient object.
+    #
+    # @param url is the url of the server
+    # @param key_file = private key file of client
+    # @param cert_file = x.509 cert containing the client's public key. This
+    #      could be a GID certificate, or any x.509 cert.
+
+    def __init__(self, url, key_file, cert_file):
+       self.url = url
+       self.key_file = key_file
+       self.cert_file = cert_file
+       self.transport = GeniTransport()
+       self.transport.key_file = self.key_file
+       self.transport.cert_file = self.cert_file
+       self.server = xmlrpclib.ServerProxy(self.url, self.transport, allow_none=True)
+
+    # -------------------------------------------------------------------------
+    # Registry Interface
+    # -------------------------------------------------------------------------
+
+    ##
+    # Create a new GID. For MAs and SAs that are physically located on the
+    # registry, this allows a owner/operator/PI to create a new GID and have it
+    # signed by his respective authority.
+    #
+    # @param cred credential of caller
+    # @param name hrn for new GID
+    # @param uuid unique identifier for new GID
+    # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
+    #
+    # @return a GID object
+
+    def create_gid(self, cred, name, uuid, pkey_string):
+        gid_str = self.server.create_gid(cred.save_to_string(save_parents=True), name, uuid, pkey_string)
+        return GID(string=gid_str)
+
+    ##
+    # Retrieve the GID for an object. This function looks up a record in the
+    # registry and returns the GID of the record if it exists.
+    # TODO: Is this function needed? It's a shortcut for Resolve()
+    #
+    # @param name hrn to look up
+    #
+    # @return a GID object
+
+    def get_gid(self, name):
+       gid_str_list = self.server.get_gid(name)
+       gid_list = []
+       for str in gid_str_list:
+           gid_list.append(GID(string=str))
+       return gid_list
+
+    ##
+    # Get_self_credential a degenerate version of get_credential used by a
+    # client to get his initial credential when he doesn't have one. This is
+    # the same as get_credential(..., cred=None,...).
+    #
+    # The registry ensures that the client is the principal that is named by
+    # (type, name) by comparing the public key in the record's GID to the
+    # private key used to encrypt the client-side of the HTTPS connection. Thus
+    # it is impossible for one principal to retrieve another principal's
+    # credential without having the appropriate private key.
+    #
+    # @param type type of object (user | slice | sa | ma | node
+    # @param name human readable name of object
+    #
+    # @return a credential object
+
+    def get_self_credential(self, type, name):
+        cred_str = self.server.get_self_credential(type, name)
+        return Credential(string = cred_str)
+
+    ##
+    # Retrieve a credential for an object.
+    #
+    # If cred==None, then the behavior reverts to get_self_credential()
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param type type of object (user | slice | sa | ma | node)
+    # @param name human readable name of object
+    #
+    # @return a credental object
+
+    def get_credential(self, cred, type, name):
+        if cred == None:
+            return self.get_self_credential(type, name)
+        cred_str = self.server.get_credential(cred.save_to_string(save_parents=True), type, name)
+        return Credential(string = cred_str)
+
+    ##
+    # List the records in an authority. The objectGID in the supplied credential
+    # should name the authority that will be listed.
+    #
+    # @param cred credential object specifying rights of the caller
+    #
+    # @return list of record objects
+
+    def list(self, cred, auth_hrn):
+        result_dict_list = self.server.list(cred.save_to_string(save_parents=True), auth_hrn)
+        result_rec_list = []
+        for dict in result_dict_list:
+             result_rec_list.append(GeniRecord(dict=dict))
+        return result_rec_list
+
+    ##
+    # Register an object with the registry. In addition to being stored in the
+    # Geni database, the appropriate records will also be created in the
+    # PLC databases.
+    #
+    # The geni_info and/or pl_info fields must in the record must be filled
+    # out correctly depending on the type of record that is being registered.
+    #
+    # TODO: The geni_info member of the record should be parsed and the pl_info
+    # adjusted as necessary (add/remove users from a slice, etc)
+    #
+    # @param cred credential object specifying rights of the caller
+    # @return record to register
+    #
+    # @return GID object for the newly-registered record
+
+    def register(self, cred, record):
+        gid_str = self.server.register(cred.save_to_string(save_parents=True), record.as_dict())
+        return GID(string = gid_str)
+
+    ##
+    # Remove an object from the registry. If the object represents a PLC object,
+    # then the PLC records will also be removed.
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param type
+    # @param hrn
+
+    def remove(self, cred, type, hrn):
+        result = self.server.remove(cred.save_to_string(save_parents=True), type, hrn)
+        return result
+
+    ##
+    # Resolve an object in the registry. A given HRN may have multiple records
+    # associated with it, and therefore multiple records may be returned. The
+    # caller should check the type fields of the records to find the one that
+    # he is interested in.
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param name human readable name of object
+
+    def resolve(self, cred, name):
+        result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name)
+        result_rec_list = []
+        for dict in result_dict_list:
+             result_rec_list.append(GeniRecord(dict=dict))
+        return result_rec_list
+
+    ##
+    # Update an object in the registry. Currently, this only updates the
+    # PLC information associated with the record. The Geni fields (name, type,
+    # GID) are fixed.
+    #
+    # The record is expected to have the pl_info field filled in with the data
+    # that should be updated.
+    #
+    # TODO: The geni_info member of the record should be parsed and the pl_info
+    # adjusted as necessary (add/remove users from a slice, etc)
+    #
+    # @param cred credential object specifying rights of the caller
+    # @param record a record object to be updated
+
+    def update(self, cred, record):
+        result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict())
+        return result
+
+    # ------------------------------------------------------------------------
+    # Slice Interface
+    # ------------------------------------------------------------------------
+
+    ##
+    # Start a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def start_slice(self, cred):
+        result = self.server.start_slice(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # Stop a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def stop_slice(self, cred):
+        result = self.server.stop_slice(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # Reset a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def reset_slice(self, cred):
+        result = self.server.reset_slice(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # Delete a slice.
+    #
+    # @param cred a credential identifying the caller (callerGID) and the slice
+    #     (objectGID)
+
+    def delete_slice(self, cred):
+        result = self.server.delete_slice(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # List the slices on a component.
+    #
+    # @param cred credential object that authorizes the caller
+    #
+    # @return a list of slice names
+
+    def list_slices(self, cred):
+        result = self.server.list_slices(cred.save_to_string(save_parents=True))
+        return result
+
+    ##
+    # Retrieve a ticket. This operation is currently implemented on the
+    # registry (see SFA, engineering decisions), and is not implemented on
+    # components.
+    #
+    # The ticket is filled in with information from the PLC database. This
+    # information includes resources, and attributes such as user keys and
+    # initscripts.
+    #
+    # @param cred credential object
+    # @param name name of the slice to retrieve a ticket for
+    # @param rspec resource specification dictionary
+    #
+    # @return a ticket object
+
+    def get_ticket(self, cred, name, rspec):
+        ticket_str = self.server.get_ticket(cred.save_to_string(save_parents=True), name, rspec)
+        ticket = Ticket(string=ticket_str)
+        return ticket
+
+    ##
+    # Redeem a ticket. This operation is currently implemented on the
+    # component.
+    #
+    # The ticket is submitted to the node manager, and the slice is instantiated
+    # or updated as appropriate.
+    #
+    # TODO: This operation should return a sliver credential and indicate
+    # whether or not the component will accept only sliver credentials, or
+    # will accept both sliver and slice credentials.
+    #
+    # @param ticket a ticket object containing the ticket
+
+    def redeem_ticket(self, ticket):
+        result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))
+        return result
+
+
diff --git a/geni/util/geniserver.py b/geni/util/geniserver.py
new file mode 100644 (file)
index 0000000..b997571
--- /dev/null
@@ -0,0 +1,246 @@
+##
+# This module implements a general-purpose server layer for geni.
+# The same basic server should be usable on the registry, component, or
+# other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+##
+
+import SimpleXMLRPCServer
+
+import sys
+import traceback
+import threading
+import SocketServer
+import BaseHTTPServer
+import SimpleHTTPServer
+import SimpleXMLRPCServer
+
+from excep import *
+from cert import *
+from credential import *
+
+import socket, os
+from OpenSSL import SSL
+
+##
+# Verification callback for pyOpenSSL. We do our own checking of keys because
+# we have our own authentication spec. Thus we disable several of the normal
+# prohibitions that OpenSSL places on certificates
+
+def verify_callback(conn, x509, err, depth, preverify):
+    # if the cert has been preverified, then it is ok
+    if preverify:
+       #print "  preverified"
+       return 1
+
+    # we're only passing single certificates, not chains
+    if depth > 0:
+       #print "  depth > 0 in verify_callback"
+       return 0
+
+    # create a Certificate object and load it from the client's x509
+    ctx = conn.get_context()
+    server = ctx.get_app_data()
+    server.peer_cert = Certificate()
+    server.peer_cert.load_from_pyopenssl_x509(x509)
+
+    # the certificate verification done by openssl checks a number of things
+    # that we aren't interested in, so we look out for those error messages
+    # and ignore them
+
+    # XXX SMBAKER: I don't know what this error is, but it's being returned
+    # by newer pl nodes.
+    if err == 9:
+       #print "  X509_V_ERR_CERT_NOT_YET_VALID"
+       return 1
+
+    # allow self-signed certificates
+    if err == 18:
+       #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+       return 1
+
+    # allow certs that don't have an issuer
+    if err == 20:
+       #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+       return 1
+
+    # allow certs that are untrusted
+    if err == 21:
+       #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+       return 1
+
+    # allow certs that are untrusted
+    if err == 27:
+       #print "  X509_V_ERR_CERT_UNTRUSTED"
+       return 1
+
+    print "  error", err, "in verify_callback"
+
+    return 0\r
+
+##
+# Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
+
+class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+    def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
+        """Secure XML-RPC server.
+
+        It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
+        """
+        self.logRequests = logRequests
+
+        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
+        SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
+        ctx = SSL.Context(SSL.SSLv23_METHOD)
+        ctx.use_privatekey_file(key_file)
+        ctx.use_certificate_file(cert_file)
+        ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
+        ctx.set_app_data(self)
+        self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
+                                                        self.socket_type))
+        self.server_bind()
+        self.server_activate()
+
+    # _dispatch
+    #
+    # Convert an exception on the server to a full stack trace and send it to
+    # the client.
+
+    def _dispatch(self, method, params):
+        try:
+            return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
+        except:
+            # can't use format_exc() as it is not available in jython yet
+            # (evein in trunk).
+            type, value, tb = sys.exc_info()
+            raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
+
+##
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
+
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+    """Secure XML-RPC request handler class.
+
+    It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
+    """
+    def setup(self):
+        self.connection = self.request
+        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+    def do_POST(self):
+        """Handles the HTTPS POST request.
+
+        It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
+        """
+
+        try:
+            # get arguments
+            data = self.rfile.read(int(self.headers["content-length"]))
+            # In previous versions of SimpleXMLRPCServer, _dispatch
+            # could be overridden in this class, instead of in
+            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
+            # check to see if a subclass implements _dispatch and dispatch
+            # using that method if present.
+            response = self.server._marshaled_dispatch(
+                    data, getattr(self, '_dispatch', None)
+                )
+        except: # This should only happen if the module is buggy
+            # internal error, report as HTTP server error
+            self.send_response(500)
+
+            self.end_headers()
+        else:
+            # got a valid XML RPC response
+            self.send_response(200)
+            self.send_header("Content-type", "text/xml")
+            self.send_header("Content-length", str(len(response)))
+            self.end_headers()
+            self.wfile.write(response)
+
+            # shut down the connection
+            self.wfile.flush()
+            self.connection.shutdown() # Modified here!
+
+##
+# Implements an HTTPS XML-RPC server. Generally it is expected that GENI
+# functions will take a credential string, which is passed to
+# decode_authentication. Decode_authentication() will verify the validity of
+# the credential, and verify that the user is using the key that matches the
+# GID supplied in the credential.
+
+class GeniServer(threading.Thread):
+
+    ##
+    # Create a new GeniServer object.
+    #
+    # @param ip the ip address to listen on
+    # @param port the port to listen on
+    # @param key_file private key filename of registry
+    # @param cert_file certificate filename containing public key 
+    #   (could be a GID file)
+
+    def __init__(self, ip, port, key_file, cert_file):
+        threading.Thread.__init__(self)
+        self.key = Keypair(filename = key_file)
+        self.cert = Certificate(filename = cert_file)
+        self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+        self.trusted_cert_list = None
+        self.register_functions()
+
+    ##
+    # Decode the credential string that was submitted by the caller. Several
+    # checks are performed to ensure that the credential is valid, and that the
+    # callerGID included in the credential matches the caller that is
+    # connected to the HTTPS connection.
+
+    def decode_authentication(self, cred_string, operation):
+        self.client_cred = Credential(string = cred_string)
+        self.client_gid = self.client_cred.get_gid_caller()
+        self.object_gid = self.client_cred.get_gid_object()
+
+        # make sure the client_gid is not blank
+        if not self.client_gid:
+            raise MissingCallerGID(self.client_cred.get_subject())
+
+        # make sure the client_gid matches client's certificate
+        peer_cert = self.server.peer_cert
+        if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
+            raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
+
+        # make sure the client is allowed to perform the operation
+        if operation:
+            if not self.client_cred.can_perform(operation):
+                raise InsufficientRights(operation)
+
+        if self.trusted_cert_list:
+            self.client_cred.verify_chain(self.trusted_cert_list)
+            if self.client_gid:
+                self.client_gid.verify_chain(self.trusted_cert_list)
+            if self.object_gid:
+                self.object_gid.verify_chain(self.trusted_cert_list)
+
+    ##
+    # Register functions that will be served by the XMLRPC server. This
+    # function should be overrided by each descendant class.
+
+    def register_functions(self):
+        self.server.register_function(self.noop)
+
+    ##
+    # Sample no-op server function. The no-op function decodes the credential
+    # that was passed to it.
+
+    def noop(self, cred, anything):
+        self.decode_authentication(cred)
+
+        return anything
+
+    ##
+    # Execute the server, serving requests forever. 
+
+    def run(self):
+        self.server.serve_forever()
+
+
diff --git a/geni/util/genitable.py b/geni/util/genitable.py
new file mode 100644 (file)
index 0000000..53b69c6
--- /dev/null
@@ -0,0 +1,124 @@
+# genitable.py
+#
+# implements support for geni records stored in db tables
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+
+import report
+
+from pg import DB
+from gid import *
+from record import *
+
+GENI_TABLE_PREFIX = "geni$"
+
+class GeniTable():
+    def __init__(self, create=False, hrn="unspecified.default.registry", cninfo=None):
+        global GENI_TABLE_PREFIX
+
+        self.hrn = hrn
+
+        # pgsql doesn't like table names with "." in them, to replace it with "$"
+        self.tablename = GENI_TABLE_PREFIX + self.hrn.replace(".", "$")
+
+        # establish a connection to the pgsql server
+        self.cnx = DB(cninfo['dbname'], cninfo['address'], port=cninfo['port'], user=cninfo['user'], passwd=cninfo['password'])
+
+        # if asked to create the table, then create it
+        if create:
+            self.create()
+
+    def exists(self):
+        tableList = self.cnx.get_tables()
+        if 'public.' + self.tablename in tableList:
+            return True
+        if 'public."' + self.tablename + '"' in tableList:
+            return True
+        return False
+
+    def create(self):
+        querystr = "CREATE TABLE " + self.tablename + " ( \
+                key text, \
+                name text, \
+                gid text, \
+                type text, \
+                pointer integer);"
+
+        self.cnx.query('DROP TABLE IF EXISTS ' + self.tablename)
+        self.cnx.query(querystr)
+
+    def remove(self, record):
+        query_str = "DELETE FROM " + self.tablename + " WHERE key = '" + record.get_key() + "'"
+        self.cnx.query(query_str)
+
+    def insert(self, record):
+        fieldnames = ["key"] + record.get_field_names()
+        fieldvals = record.get_field_value_strings(fieldnames)
+        query_str = "INSERT INTO " + self.tablename + \
+                       "(" + ",".join(fieldnames) + ") " + \
+                       "VALUES(" + ",".join(fieldvals) + ")"
+        #print query_str
+        self.cnx.query(query_str)
+
+    def update(self, record):
+        names = record.get_field_names()
+        pairs = []
+        for name in names:
+           val = record.get_field_value_string(name)
+           pairs.append(name + " = " + val)
+        update = ", ".join(pairs)
+
+        query_str = "UPDATE " + self.tablename+ " SET " + update + " WHERE key = '" + record.get_key() + "'"
+        #print query_str
+        self.cnx.query(query_str)
+
+    def find_dict(self, type, value, searchfield):
+        query_str = "SELECT * FROM " + self.tablename + " WHERE " + searchfield + " = '" + str(value) + "'"
+        dict_list = self.cnx.query(query_str).dictresult()
+        result_dict_list = []
+        for dict in dict_list:
+           if (type=="*") or (dict['type'] == type):
+               result_dict_list.append(dict)
+        return result_dict_list
+
+    def find(self, type, value, searchfield):
+        result_dict_list = self.find_dict(type, value, searchfield)
+        result_rec_list = []
+        for dict in result_dict_list:
+            result_rec_list.append(GeniRecord(dict=dict))
+        return result_rec_list
+
+    def resolve_dict(self, type, hrn):
+        return self.find_dict(type, hrn, "name")
+
+    def resolve(self, type, hrn):
+        return self.find(type, hrn, "name")
+
+    def list_dict(self):
+        query_str = "SELECT * FROM " + self.tablename
+        result_dict_list = self.cnx.query(query_str).dictresult()
+        return result_dict_list
+
+    def list(self):
+        result_dict_list = self.list_dict()
+        result_rec_list = []
+        for dict in result_dict_list:
+            result_rec_list.append(GeniRecord(dict=dict))
+        return result_rec_list
+
+def set_geni_table_prefix(x):
+    global GENI_TABLE_PREFIX
+
+    GENI_TABLE_PREFIX = x
+
+def geni_records_purge(cninfo):
+    global GENI_TABLE_PREFIX
+
+    cnx = DB(cninfo['dbname'], cninfo['address'], port=cninfo['port'], user=cninfo['user'], passwd=cninfo['password'])
+    tableList = cnx.get_tables()
+    for table in tableList:
+        if table.startswith(GENI_TABLE_PREFIX) or \
+           table.startswith('public.' + GENI_TABLE_PREFIX) or \
+           table.startswith('public."' + GENI_TABLE_PREFIX):
+               report.trace("dropping table " + table)
+               cnx.query("DROP TABLE " + table)
diff --git a/geni/util/geniticket.py b/geni/util/geniticket.py
new file mode 100644 (file)
index 0000000..1869e58
--- /dev/null
@@ -0,0 +1,128 @@
+# tickets.py
+#
+# implements GENI tickets
+#
+
+from cert import *
+from rights import *
+from gid import *
+import xmlrpclib
+
+# Ticket is tuple:
+#   (gidCaller, gidObject, attributes, rspec, delegate)
+#
+#    gidCaller = GID of the caller performing the operation
+#    gidObject = GID of the slice
+#    attributes = slice attributes (keys, vref, instantiation, etc)
+#    rspec = resources
+
+class Ticket(Certificate):
+    gidCaller = None
+    gidObject = None
+    attributes = {}
+    rspec = {}
+    delegate = False
+
+    def __init__(self, create=False, subject=None, string=None, filename=None):
+        Certificate.__init__(self, create, subject, string, filename)
+
+    def set_gid_caller(self, gid):
+        self.gidCaller = gid
+
+    def get_gid_caller(self):
+        if not self.gidCaller:
+            self.decode()
+        return self.gidCaller
+
+    def set_gid_object(self, gid):
+        self.gidObject = gid
+
+    def get_gid_object(self):
+        if not self.gidObject:
+            self.decode()
+        return self.gidObject
+
+    def set_attributes(self, gid):
+        self.attributes = gid
+
+    def get_attributes(self):
+        if not self.attributes:
+            self.decode()
+        return self.attributes
+
+    def set_rspec(self, gid):
+        self.rspec = gid
+
+    def get_rspec(self):
+        if not self.rspec:
+            self.decode()
+        return self.rspec
+
+    def set_delegate(self, delegate):
+        self.delegate = delegate
+
+    def get_delegate(self):
+        if not self.delegate:
+            self.decode()
+        return self.delegate
+
+    def encode(self):
+        dict = {"gidCaller": None,
+                "gidObject": None,
+                "attributes": self.attributes,
+                "rspec": self.rspec,
+                "delegate": self.delegate}
+        if self.gidCaller:
+            dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True)
+        if self.gidObject:
+            dict["gidObject"] = self.gidObject.save_to_string(save_parents=True)
+        str = xmlrpclib.dumps((dict,), allow_none=True)
+        self.set_data(str)
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_data())[0][0]
+        else:
+            dict = {}
+
+        self.attributes = dict.get("attributes", {})
+        self.rspec = dict.get("rspec", {})
+        self.delegate = dict.get("delegate", False)
+
+        gidCallerStr = dict.get("gidCaller", None)
+        if gidCallerStr:
+            self.gidCaller = GID(string=gidCallerStr)
+        else:
+            self.gidCaller = None
+
+        gidObjectStr = dict.get("gidObject", None)
+        if gidObjectStr:
+            self.gidObject = GID(string=gidObjectStr)
+        else:
+            self.gidObject = None
+
+    def dump(self, dump_parents=False):
+        print "TICKET", self.get_subject()
+
+        print "  gidCaller:"
+        gidCaller = self.get_gid_caller()
+        if gidCaller:
+            gidCaller.dump(8, dump_parents)
+
+        print "  gidObject:"
+        gidObject = self.get_gid_object()
+        if gidObject:
+            gidObject.dump(8, dump_parents)
+
+        print "  attributes:"
+        for attrname in self.get_attributes().keys():
+            print "        ", attrname, self.get_attributes()[attrname]
+
+        print "       rspec:"
+        for attrname in self.get_rspec().keys():
+            print "        ", attrname, self.get_rspec()[attrname]
+
+        if self.parent and dump_parents:
+           print "PARENT",
+           self.parent.dump(dump_parents)
diff --git a/geni/util/gid.py b/geni/util/gid.py
new file mode 100644 (file)
index 0000000..65aff6b
--- /dev/null
@@ -0,0 +1,136 @@
+##
+# Implements GENI GID. GIDs are based on certificates, and the GID class is a
+# descendant of the certificate class.
+##
+
+from cert import *
+import uuid
+import xmlrpclib
+
+##
+# Create a new uuid. Returns the UUID as a string.
+
+def create_uuid():
+    return str(uuid.uuid4().int)
+
+##
+# GID is a tuplie:
+#    (uuid, hrn, public_key)
+#
+# UUID is a unique identifier and is created by the python uuid module
+#    (or the utility function create_uuid() in gid.py).
+#
+# HRN is a human readable name. It is a dotted form similar to a backward domain\r
+#    name. For example, planetlab.us.arizona.bakers.\r
+#\r
+# PUBLIC_KEY is the public key of the principal identified by the UUID/HRN.\r
+# It is a Keypair object as defined in the cert.py module.\r
+#\r
+# It is expected that there is a one-to-one pairing between UUIDs and HRN,
+# but it is uncertain how this would be inforced or if it needs to be enforced.
+#
+# These fields are encoded using xmlrpc into the subjectAltName field of the
+# x509 certificate. Note: Call encode() once the fields have been filled in
+# to perform this encoding.
+
+
+class GID(Certificate):
+    uuid = None
+    hrn = None
+
+    ##
+    # Create a new GID object
+    #
+    # @param create If true, create the X509 certificate
+    # @param subject If subject!=None, create the X509 cert and set the subject name
+    # @param string If string!=None, load the GID from a string
+    # @param filename If filename!=None, load the GID from a file
+
+    def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None):
+        Certificate.__init__(self, create, subject, string, filename)
+        if uuid:
+            self.uuid = uuid
+        if hrn:
+            self.hrn = hrn
+
+    def set_uuid(self, uuid):
+        self.uuid = uuid
+
+    def get_uuid(self):
+        if not self.uuid:
+            self.decode()
+        return self.uuid
+
+    def set_hrn(self, hrn):
+        self.hrn = hrn
+
+    def get_hrn(self):
+        if not self.hrn:
+            self.decode()
+        return self.hrn
+
+    ##
+    # Encode the GID fields and package them into the subject-alt-name field
+    # of the X509 certificate. This must be called prior to signing the
+    # certificate. It may only be called once per certificate.
+
+    def encode(self):
+        dict = {"uuid": self.uuid,
+                "hrn": self.hrn}
+        str = xmlrpclib.dumps((dict,))
+        self.set_data(str)
+
+    ##
+    # Decode the subject-alt-name field of the X509 certificate into the
+    # fields of the GID. This is automatically called by the various get_*()
+    # functions in this class.
+
+    def decode(self):
+        data = self.get_data()
+        if data:
+            dict = xmlrpclib.loads(self.get_data())[0][0]
+        else:
+            dict = {}
+
+        self.uuid = dict.get("uuid", None)
+        self.hrn = dict.get("hrn", None)
+
+    ##
+    # Dump the credential to stdout.
+    #
+    # @param indent specifies a number of spaces to indent the output
+    # @param dump_parents If true, also dump the parents of the GID
+
+    def dump(self, indent=0, dump_parents=False):
+        print " "*indent, " hrn:", self.get_hrn()
+        print " "*indent, "uuid:", self.get_uuid()
+
+        if self.parent and dump_parents:
+            print " "*indent, "parent:"
+            self.parent.dump(indent+4)
+
+    ##
+    # Verify the chain of authenticity of the GID. First perform the checks
+    # of the certificate class (verifying that each parent signs the child,
+    # etc). In addition, GIDs also confirm that the parent's HRN is a prefix
+    # of the child's HRN.
+    #
+    # Verifying these prefixes prevents a rogue authority from signing a GID
+    # for a principal that is not a member of that authority. For example,
+    # planetlab.us.arizona cannot sign a GID for planetlab.us.princeton.foo.
+
+    def verify_chain(self, trusted_certs = None):
+        # do the normal certificate verification stuff
+        Certificate.verify_chain(self, trusted_certs)
+
+        if self.parent:
+            # make sure the parent's hrn is a prefix of the child's hrn
+            if not self.get_hrn().startswith(self.parent.get_hrn()):
+                raise GidParentHrn(self.parent.get_subject())
+
+        return
+
+
+
+
+
diff --git a/geni/util/hierarchy.py b/geni/util/hierarchy.py
new file mode 100644 (file)
index 0000000..a96ec56
--- /dev/null
@@ -0,0 +1,330 @@
+##
+# This module implements a hierarchy of authorities and performs a similar
+# function as the "tree" module of the original geniwrapper prototype. An HRN
+# is assumed to be a string of authorities separated by dots. For example,
+# "planetlab.us.arizona.bakers". Each component of the HRN is a different
+# authority, with the last component being a leaf in the tree.
+#
+# Each authority is stored in a subdirectory on the registry. Inside this
+# subdirectory are several files:
+#      *.GID - GID file
+#      *.PKEY - private key file
+#      *.DBINFO - database info
+##
+
+import os
+import report
+from cert import *
+from credential import *
+from gid import *
+from misc import *
+from config import *
+from geniticket import *
+
+##
+# The AuthInfo class contains the information for an authority. This information
+# includes the GID, private key, and database connection information.
+
+class AuthInfo():
+    hrn = None
+    gid_object = None
+    gid_filename = None
+    privkey_filename = None
+    dbinfo_filename = None
+
+    ##
+    # Initialize and authority object.
+    #
+    # @param hrn the human readable name of the authority
+    # @param gid_filename the filename containing the GID
+    # @param privkey_filename the filename containing the private key
+    # @param dbinfo_filename the filename containing the database info
+
+    def __init__(self, hrn, gid_filename, privkey_filename, dbinfo_filename):
+        self.hrn = hrn
+        self.set_gid_filename(gid_filename)
+        self.privkey_filename = privkey_filename
+        self.dbinfo_filename = dbinfo_filename
+
+    ##
+    # Set the filename of the GID
+    #
+    # @param fn filename of file containing GID
+
+    def set_gid_filename(self, fn):
+        self.gid_filename = fn
+        self.gid_object = None
+
+    ##
+    # Get the GID in the form of a GID object
+
+    def get_gid_object(self):
+        if not self.gid_object:
+            self.gid_object = GID(filename = self.gid_filename)
+        return self.gid_object
+
+    ##
+    # Get the private key in the form of a Keypair object
+
+    def get_pkey_object(self):
+        return Keypair(filename = self.privkey_filename)
+
+    ##
+    # Get the dbinfo in the form of a dictionary
+
+    def get_dbinfo(self):
+        f = file(self.dbinfo_filename)
+        dict = eval(f.read())\r
+        f.close()\r
+        return dict\r
+\r
+    ##\r
+    # Replace the GID with a new one. The file specified by gid_filename is\r
+    # overwritten with the new GID object\r
+    #\r
+    # @param gid object containing new GID\r
+\r
+    def update_gid_object(self, gid):\r
+        gid.save_to_file(self.gid_filename)\r
+        self.gid_object = gid\r
+\r
+##\r
+# The Hierarchy class is responsible for managing the tree of authorities.\r
+# Each authority is a node in the tree and exists as an AuthInfo object.\r
+#\r
+# The tree is stored on disk in a hierarchical manner than reflects the\r
+# structure of the tree. Each authority is a subdirectory, and each subdirectory\r
+# contains the GID, pkey, and dbinfo files for that authority (as well as\r
+# subdirectories for each sub-authority)\r
+
+class Hierarchy():
+    ##
+    # Create the hierarchy object.
+    #
+    # @param basedir the base directory to store the hierarchy in
+
+    def __init__(self, basedir="."):
+        self.basedir = os.path.join(basedir, "authorities")
+
+    ##
+    # Given a hrn, return the filenames of the GID, private key, and dbinfo
+    # files.
+    #
+    # @param hrn the human readable name of the authority
+
+    def get_auth_filenames(self, hrn):
+        leaf = get_leaf(hrn)
+        parent_hrn = get_authority(hrn)
+        directory = os.path.join(self.basedir, hrn.replace(".", "/"))
+
+        gid_filename = os.path.join(directory, leaf+".gid")
+        privkey_filename = os.path.join(directory, leaf+".pkey")
+        dbinfo_filename = os.path.join(directory, leaf+".dbinfo")
+
+        return (directory, gid_filename, privkey_filename, dbinfo_filename)
+
+    ##
+    # Check to see if an authority exists. An authority exists if it's disk
+    # files exist.
+    #
+    # @param the human readable name of the authority to check
+
+    def auth_exists(self, hrn):
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+
+        return os.path.exists(gid_filename) and \
+               os.path.exists(privkey_filename) and \
+               os.path.exists(dbinfo_filename)
+
+    ##
+    # Create an authority. A private key for the authority and the associated
+    # GID are created and signed by the parent authority.
+    #
+    # @param hrn the human readable name of the authority to create
+    # @param create_parents if true, also create the parents if they do not exist
+
+    def create_auth(self, hrn, create_parents=False):
+        report.trace("Hierarchy: creating authority: " + hrn)
+
+        # create the parent authority if necessary
+        parent_hrn = get_authority(hrn)
+        if (parent_hrn) and (not self.auth_exists(parent_hrn)) and (create_parents):
+            self.create_auth(parent_hrn, create_parents)
+
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+
+        # create the directory to hold the files
+        try:
+            os.makedirs(directory)\r
+        # if the path already exists then pass\r
+        except OSError, (errno, strerr):\r
+            if errno == 17:\r
+                pass
+
+        pkey = Keypair(create = True)
+        pkey.save_to_file(privkey_filename)
+
+        gid = self.create_gid(hrn, create_uuid(), pkey)
+        gid.save_to_file(gid_filename, save_parents=True)
+
+        # XXX TODO: think up a better way for the dbinfo to work
+
+        dbinfo = get_default_dbinfo()
+        dbinfo_file = file(dbinfo_filename, "w")
+        dbinfo_file.write(str(dbinfo))\r
+        dbinfo_file.close()
+
+    ##
+    # Return the AuthInfo object for the specified authority. If the authority
+    # does not exist, then an exception is thrown. As a side effect, disk files
+    # and a subdirectory may be created to store the authority.
+    #
+    # @param hrn the human readable name of the authority to create.
+
+    def get_auth_info(self, hrn):
+        #report.trace("Hierarchy: getting authority: " + hrn)
+
+        if not self.auth_exists(hrn):
+            raise MissingAuthority(hrn)
+
+        (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+            self.get_auth_filenames(hrn)
+
+        auth_info = AuthInfo(hrn, gid_filename, privkey_filename, dbinfo_filename)
+
+        # check the GID and see if it needs to be refreshed
+        gid = auth_info.get_gid_object()
+        gid_refreshed = self.refresh_gid(gid)
+        if gid != gid_refreshed:
+            auth_info.update_gid_object(gid_refreshed)
+
+        return auth_info
+
+    ##
+    # Create a new GID. The GID will be signed by the authority that is it's
+    # immediate parent in the hierarchy (and recursively, the parents' GID
+    # will be signed by its parent)
+    #
+    # @param hrn the human readable name to store in the GID
+    # @param uuid the unique identifier to store in the GID
+    # @param pkey the public key to store in the GID
+
+    def create_gid(self, hrn, uuid, pkey):
+        gid = GID(subject=hrn, uuid=uuid, hrn=hrn)
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            gid.set_issuer(pkey, hrn)
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            gid.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+            gid.set_parent(parent_auth_info.get_gid_object())
+
+        gid.set_pubkey(pkey)
+        gid.encode()
+        gid.sign()
+
+        return gid
+
+    ##
+    # Refresh a GID. The primary use of this function is to refresh the
+    # the expiration time of the GID. It may also be used to change the HRN,
+    # UUID, or Public key of the GID.
+    #
+    # @param gid the GID to refresh
+    # @param hrn if !=None, change the hrn
+    # @param uuid if !=None, change the uuid
+    # @param pubkey if !=None, change the public key
+
+    def refresh_gid(self, gid, hrn=None, uuid=None, pubkey=None):
+        # TODO: compute expiration time of GID, refresh it if necessary
+        gid_is_expired = False
+
+        # update the gid if we need to
+        if gid_is_expired or hrn or uuid or pubkey:
+            if not hrn:
+                hrn = gid.get_hrn()
+            if not uuid:
+                uuid = gid.get_uuid()
+            if not pubkey:
+                pubkey = gid.get_pubkey()
+
+            gid = self.create_gid(hrn, uuid, pubkey)
+
+        return gid
+
+    ##
+    # Retrieve an authority credential for an authority. The authority
+    # credential will contain the authority privilege and will be signed by
+    # the authority's parent.
+    #
+    # @param hrn the human readable name of the authority
+
+    def get_auth_cred(self, hrn):
+        auth_info = self.get_auth_info(hrn)
+        gid = auth_info.get_gid_object()
+
+        cred = Credential(subject=hrn)
+        cred.set_gid_caller(gid)
+        cred.set_gid_object(gid)
+        cred.set_privileges("authority")
+        cred.set_delegate(True)
+        cred.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            cred.set_issuer(auth_info.get_pkey_object(), hrn)
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            cred.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+            cred.set_parent(self.get_auth_cred(parent_hrn))
+
+        cred.encode()
+        cred.sign()
+
+        return cred
+    ##
+    # Retrieve an authority ticket. An authority ticket is not actually a
+    # redeemable ticket, but only serves the purpose of being included as the
+    # parent of another ticket, in order to provide a chain of authentication
+    # for a ticket.
+    #
+    # This looks almost the same as get_auth_cred, but works for tickets
+    # XXX does similarity imply there should be more code re-use?
+    #
+    # @param hrn the human readable name of the authority
+
+    def get_auth_ticket(self, hrn):
+        auth_info = self.get_auth_info(hrn)
+        gid = auth_info.get_gid_object()
+
+        ticket = Ticket(subject=hrn)
+        ticket.set_gid_caller(gid)
+        ticket.set_gid_object(gid)
+        ticket.set_delegate(True)
+        ticket.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+        parent_hrn = get_authority(hrn)
+        if not parent_hrn:
+            # if there is no parent hrn, then it must be self-signed. this
+            # is where we terminate the recursion
+            ticket.set_issuer(auth_info.get_pkey_object(), hrn)
+        else:
+            # we need the parent's private key in order to sign this GID
+            parent_auth_info = self.get_auth_info(parent_hrn)
+            ticket.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+            ticket.set_parent(self.get_auth_cred(parent_hrn))
+
+        ticket.encode()
+        ticket.sign()
+
+        return ticket
+
diff --git a/geni/util/misc.py b/geni/util/misc.py
new file mode 100644 (file)
index 0000000..bd703b7
--- /dev/null
@@ -0,0 +1,31 @@
+from excep import *
+
+def get_leaf(hrn):
+    parts = hrn.split(".")
+    return ".".join(parts[-1:])
+
+def get_authority(hrn):
+    parts = hrn.split(".")
+    return ".".join(parts[:-1])
+
+def get_auth_type(type):
+    if (type=="slice") or (type=="user") or (type=="sa"):
+        return "sa"
+    elif (type=="component") or (type=="ma"):
+        return "ma"
+    else:
+        raise UnknownGeniType(type)
+
+def hrn_to_pl_slicename(hrn):
+    parts = hrn.split(".")
+    return parts[-2] + "_" + parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc authority name
+def hrn_to_pl_authname(hrn):
+    parts = hrn.split(".")
+    return parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc login_base
+def hrn_to_pl_login_base(hrn):
+    return hrn_to_pl_authname(hrn)
+
diff --git a/geni/util/record.py b/geni/util/record.py
new file mode 100644 (file)
index 0000000..086723d
--- /dev/null
@@ -0,0 +1,285 @@
+##
+# Implements support for geni records
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+##
+
+import report
+from gid import *
+
+##
+# The GeniRecord class implements a Geni Record. A GeniRecord is a tuple
+# (Name, GID, Type, Info).
+#
+# Name specifies the HRN of the object
+# GID is the GID of the object
+# Type is user | sa | ma | slice | component
+#
+# Info is comprised of the following sub-fields
+#        pointer = a pointer to the record in the PL database
+#        pl_info = planetlab-specific info (when talking to client)
+#        geni_info = geni-specific info (when talking to client)
+#
+# The pointer is interpreted depending on the type of the record. For example,
+# if the type=="user", then pointer is assumed to be a person_id that indexes
+# into the persons table.
+#
+# A given HRN may have more than one record, provided that the records are
+# of different types. For example, planetlab.us.arizona may have both an SA
+# and a MA record, but cannot have two SA records.
+
+class GeniRecord():
+
+    ##
+    # Create a Geni Record
+    #
+    # @param name if !=None, assign the name of the record
+    # @param gid if !=None, assign the gid of the record
+    # @param type one of user | sa | ma | slice | component
+    # @param pointer is a pointer to a PLC record
+    # @param dict if !=None, then fill in this record from the dictionary
+
+    def __init__(self, name=None, gid=None, type=None, pointer=None, dict=None, string=None):
+        self.dirty = True
+        self.pl_info = None
+        self.geni_info = None
+        self.name = None
+        self.gid = None
+        self.type = None
+        self.pointer = None
+        if name:
+            self.set_name(name)
+        if gid:
+            self.set_gid(gid)
+        if type:
+            self.set_type(type)
+        if pointer:
+            self.set_pointer(pointer)
+        if dict:
+            self.load_from_dict(dict)
+        if string:
+            self.load_from_string(string)
+
+    ##
+    # Set the name of the record
+    #
+    # @param name is a string containing the HRN
+
+    def set_name(self, name):
+        self.name = name
+        self.dirty = True
+
+    ##
+    # Set the GID of the record
+    #
+    # @param gid is a GID object or the string representation of a GID object
+
+    def set_gid(self, gid):
+        if isinstance(gid, str):
+            self.gid = gid
+        else:
+            self.gid = gid.save_to_string(save_parents=True)
+        self.dirty = True
+
+    ##
+    # Set the type of the record
+    #
+    # @param type is a string: user | sa | ma | slice | component
+
+    def set_type(self, type):
+        self.type = type
+        self.dirty = True
+
+    ##
+    # Set the pointer of the record
+    #
+    # @param pointer is an integer containing the ID of a PLC record
+
+    def set_pointer(self, pointer):
+        self.pointer = pointer
+        self.dirty = True
+
+    ##
+    # Set the PLC info of the record
+    #
+    # @param pl_info is a dictionary containing planetlab info
+
+    def set_pl_info(self, pl_info):
+        self.pl_info = pl_info
+        self.dirty = True
+
+    ##
+    # Set the geni info the record
+    #
+    # @param geni_info is a dictionary containing geni info
+
+    def set_geni_info(self, geni_info):
+        self.geni_info = geni_info
+        self.dirty = True
+
+    ##
+    # Return the pl_info of the record, or an empty dictionary if none exists
+
+    def get_pl_info(self):
+        if self.pl_info:
+            return self.pl_info
+        else:
+            return {}
+
+    ##
+    # Return the geni_info of the record, or an empty dictionary if none exists
+
+    def get_geni_info(self):
+        if self.geni_info:
+            return self.geni_info
+        else:
+            return {}
+
+    ##
+    # Return the name (HRN) of the record
+
+    def get_name(self):
+        return self.name
+
+    ##
+    # Return the type of the record
+
+    def get_type(self):
+        return self.type
+
+    ##
+    # Return the pointer of the record. The pointer is an integer that may be
+    # used to look up the record in the PLC database. The evaluation of pointer
+    # depends on the type of the record
+
+    def get_pointer(self):
+        return self.pointer
+
+    ##
+    # Return the GID of the record, in the form of a GID object
+    # TODO: not the best name for the function, because we have things called
+    # gidObjects in the Cred
+
+    def get_gid_object(self):
+        return GID(string=self.gid)
+
+    ##
+    # Return a key that uniquely identifies this record among all records in
+    # Geni. This key is used to uniquely identify the record in the Geni
+    # database.
+
+    def get_key(self):
+        return self.name + "#" + self.type
+
+    ##
+    # Returns a list of field names in this record. pl_info, geni_info are not
+    # included because they are not part of the record that is stored in the
+    # database, but are rather computed values from other entities
+
+    def get_field_names(self):
+        return ["name", "gid", "type", "pointer"]
+
+    ##
+    # Given a field name ("name", "gid", ...) return the value of that field.
+    #
+    # @param name is the name of field to be returned
+
+    def get_field_value_string(self, fieldname):
+        if fieldname == "key":
+            val = self.get_key()
+        else:
+            val = getattr(self, fieldname)
+        if isinstance(val, str):
+            return "'" + str(val) + "'"
+        else:
+            return str(val)
+
+    ##
+    # Given a list of field names, return a list of values for those fields.
+    #
+    # @param fieldnames is a list of field names
+
+    def get_field_value_strings(self, fieldnames):
+        strs = []
+        for fieldname in fieldnames:
+            strs.append(self.get_field_value_string(fieldname))
+        return strs
+
+    ##
+    # Return the record in the form of a dictionary
+
+    def as_dict(self):
+        dict = {}
+        names = self.get_field_names()
+        for name in names:
+            dict[name] = getattr(self, name)
+
+        if self.pl_info:
+            dict['pl_info'] = self.pl_info
+
+        if self.geni_info:
+            dict['geni_info'] = self.geni_info
+
+        return dict
+
+    ##
+    # Load the record from a dictionary
+    #
+    # @param dict dictionary to load record fields from
+
+    def load_from_dict(self, dict):
+        self.set_name(dict['name'])
+        self.set_gid(dict['gid'])
+        self.set_type(dict['type'])
+        self.set_pointer(dict['pointer'])
+        if "pl_info" in dict:
+           self.set_pl_info(dict["pl_info"])
+        if "geni_info" in dict:
+           self.set_geni_info(dict["geni_info"])
+
+    ##
+    # Save the record to a string. The string contains an XML representation of
+    # the record.
+
+    def save_to_string(self):
+        dict = self.as_dict()
+        str = xmlrpclib.dumps((dict,), allow_none=True)
+        return str
+
+    ##
+    # Load the record from a string. The string is assumed to contain an XML
+    # representation of the record.
+
+    def load_from_string(self, str):
+        dict = xmlrpclib.loads(str)[0][0]
+        self.load_from_dict(dict)
+
+    ##
+    # Dump the record to stdout
+    #
+    # @param dump_parents if true, then the parents of the GID will be dumped
+
+    def dump(self, dump_parents=False):
+        print "RECORD", self.name
+        print "        hrn:", self.name
+        print "       type:", self.type
+        print "        gid:"
+        if (not self.gid):
+            print "        None"
+        else:
+            self.get_gid_object().dump(8, dump_parents)
+        print "    pointer:", self.pointer
+
+        print "  geni_info:"
+        geni_info = getattr(self, "geni_info", {})
+        if geni_info:
+            for key in geni_info.keys():
+                print "       ", key, ":", geni_info[key]
+
+        print "    pl_info:"
+        pl_info = getattr(self, "pl_info", {})
+        if pl_info:
+            for key in pl_info.keys():
+                print "       ", key, ":", pl_info[key]
+
+
diff --git a/geni/util/remoteshell.py b/geni/util/remoteshell.py
new file mode 100644 (file)
index 0000000..9697ae2
--- /dev/null
@@ -0,0 +1,98 @@
+# remoteshell.py
+#
+# interface to the PLC api via xmlrpc
+#
+# RemoteShell() exports an API that looks identical to that exported by
+# PLC.Shell.Shell(). It's meant to be a drop in replacement for running
+# geniwrapper on a different machine than PLC.
+
+import xmlrpclib
+
+class RemoteShell:
+    def __init__(self):
+        self.servers = {}
+
+    def call(self, name, pl_auth, *args):
+
+        key = pl_auth["Url"] + "#" + pl_auth["Username"]
+
+        if not (key in self.servers):
+            server = xmlrpclib.Server(pl_auth["Url"], verbose = 0, allow_none=True)
+            #server.AdmAuthCheck(pl_auth)
+            server.AuthCheck(pl_auth)
+            self.servers[key] = server
+
+        server = self.servers[key]
+
+        arglist = ["pl_auth"]
+        for arg in args:
+            arglist.append(repr(arg))
+
+        str = "server." + name + "(" + ",".join(arglist) + ")"
+        result = eval(str)
+
+        return result
+
+    # TODO: there's probably an automatic way to import all these stubs
+
+    def AddInitScript(self, pl_auth, *args):
+        return self.call("AddInitScript", pl_auth, *args)
+
+    def AddNode(self, pl_auth, *args):
+        return self.call("AddNode", pl_auth, *args)
+
+    def AddPerson(self, pl_auth, *args):
+        return self.call("AddPerson", pl_auth, *args)
+
+    def AddSite(self, pl_auth, *args):
+        return self.call("AddSite", pl_auth, *args)
+
+    def AddSlice(self, pl_auth, *args):
+        return self.call("AddSlice", pl_auth, *args)
+
+    def DeleteNode(self, pl_auth, *args):
+        return self.call("DeleteNode", pl_auth, *args)
+
+    def DeletePerson(self, pl_auth, *args):
+        return self.call("DeletePerson", pl_auth, *args)
+
+    def DeleteSite(self, pl_auth, *args):
+        return self.call("DeleteSite", pl_auth, *args)
+
+    def DeleteSlice(self, pl_auth, *args):
+        return self.call("DeleteSlice", pl_auth, *args)
+
+    def GetInitScripts(self, pl_auth, *args):
+        return self.call("GetInitScripts", pl_auth, *args)
+
+    def GetKeys(self, pl_auth, *args):
+        return self.call("GetKeys", pl_auth, *args)
+
+    def GetNodes(self, pl_auth, *args):
+        return self.call("GetNodes", pl_auth, *args)
+
+    def GetPersons(self, pl_auth, *args):
+        return self.call("GetPersons", pl_auth, *args)
+
+    def GetSites(self, pl_auth, *args):
+        return self.call("GetSites", pl_auth, *args)
+
+    def GetSliceAttributes(self, pl_auth, *args):
+        return self.call("GetSliceAttributes", pl_auth, *args)
+
+    def GetSlices(self, pl_auth, *args):
+        return self.call("GetSlices", pl_auth, *args)
+
+    def UpdateNode(self, pl_auth, *args):
+        return self.call("UpdateNode", pl_auth, *args)
+
+    def UpdatePerson(self, pl_auth, *args):
+        return self.call("UpdatePerson", pl_auth, *args)
+
+    def UpdateSite(self, pl_auth, *args):
+        return self.call("UpdateSite", pl_auth, *args)
+
+    def UpdateSlice(self, pl_auth, *args):
+        return self.call("UpdateSlice", pl_auth, *args)
+
+
diff --git a/geni/util/report.py b/geni/util/report.py
new file mode 100644 (file)
index 0000000..28dd79a
--- /dev/null
@@ -0,0 +1,5 @@
+def trace(x):
+    print x
+
+def error(x):
+    print x
diff --git a/geni/util/rights.py b/geni/util/rights.py
new file mode 100644 (file)
index 0000000..c2af9dc
--- /dev/null
@@ -0,0 +1,155 @@
+##
+# This Module implements rights and lists of rights for the Geni wrapper. Rights
+# are implemented by two classes:
+#
+# Right - represents a single right
+#
+# RightList - represents a list of rights
+#
+# A right may allow several different operations. For example, the "info" right
+# allows "listslices", "listcomponentresources", etc.
+##
+
+##
+# privilege_table is a list of priviliges and what operations are allowed
+# per privilege.
+
+privilege_table = {"authority": ["*"],
+                   "refresh": ["remove", "update"],
+                   "resolve": ["resolve", "list", "getcredential"],
+                   "sa": ["*"],
+                   "embed": ["getticket", "redeemslice", "createslice", "deleteslice", "updateslice", "getsliceresources"],
+                   "bind": ["getticket", "loanresources"],
+                   "control": ["updateslice", "createslice", "stopslice", "startslice", "deleteslice", "resetslice", "getsliceresources"],
+                   "info": ["listslices", "listnodes"],
+                   "ma": ["*"]}
+
+##
+# The Right class represents a single privilege.
+
+class Right:
+   ##
+   # Create a new right.
+   #
+   # @param kind is a string naming the right. For example "control"
+
+   def __init__(self, kind):
+      self.kind = kind
+
+   ##
+   # Test to see if this right object is allowed to perform an operation.
+   # Returns True if the operation is allowed, False otherwise.
+   #
+   # @param op_name is a string naming the operation. For example "listslices".
+
+   def can_perform(self, op_name):
+      allowed_ops = privilege_table.get(self.kind.lower(), None)
+      if not allowed_ops:
+         return False
+
+      # if "*" is specified, then all ops are permitted
+      if "*" in allowed_ops:
+         return True
+
+      return (op_name.lower() in allowed_ops)
+
+   ##
+   # Test to see if this right is a superset of a child right. A right is a
+   # superset if every operating that is allowed by the child is also allowed
+   # by this object.
+   #
+   # @param child is a Right object describing the child right
+
+   def is_superset(self, child):
+      my_allowed_ops = privilege_table.get(self.kind.lower(), None)
+      child_allowed_ops = privilege_table.get(child.kind.lower(), None)
+
+      if "*" in my_allowed_ops:
+          return True
+
+      for right in child_allowed_ops:
+          if not right in my_allowed_ops:
+              return False
+
+      return True
+
+##
+# A RightList object represents a list of privileges.
+
+class RightList:
+    ##
+    # Create a new rightlist object, containing no rights.
+    #
+    # @param string if string!=None, load the rightlist from the string
+
+    def __init__(self, string=None):
+        self.rights = []
+        if string:
+            self.load_from_string(string)
+
+    ##
+    # Add a right to this list
+    #
+    # @param right is either a Right object or a string describing the right
+
+    def add(self, right):
+        if isinstance(right, str):
+            right = Right(kind = right)
+        self.rights.append(right)
+
+    ##
+    # Load the rightlist object from a string
+
+    def load_from_string(self, string):
+        self.rights = []
+
+        # none == no rights, so leave the list empty
+        if not string:
+            return
+
+        parts = string.split(",")
+        for part in parts:
+            self.rights.append(Right(part))
+
+    ##
+    # Save the rightlist object to a string. It is saved in the format of a
+    # comma-separated list.
+
+    def save_to_string(self):
+        right_names = []
+        for right in self.rights:
+            right_names.append(right.kind)
+
+        return ",".join(right_names)
+
+    ##
+    # Check to see if some right in this list allows an operation. This is
+    # done by evaluating the can_perform function of each operation in the
+    # list.
+    #
+    # @param op_name is an operation to check, for example "listslices"
+
+    def can_perform(self, op_name):
+        for right in self.rights:
+            if right.can_perform(op_name):
+                return True
+        return False
+
+    ##
+    # Check to see if all of the rights in this rightlist are a superset
+    # of all the rights in a child rightlist. A rightlist is a superset
+    # if there is no operation in the child rightlist that cannot be
+    # performed in the parent rightlist.
+    #
+    # @param child is a rightlist object describing the child
+
+    def is_superset(self, child):
+        for child_right in child.rights:
+            allowed = False
+            for my_right in self.rights:
+                if my_right.is_superset(child_right):
+                    allowed = True
+            if not allowed:
+                return False
+        return True
+
diff --git a/geni/util/rspec.py b/geni/util/rspec.py
new file mode 100644 (file)
index 0000000..23e20f9
--- /dev/null
@@ -0,0 +1,163 @@
+import sys
+import pprint
+import os
+from xml.dom import minidom
+from types import StringTypes
+
+class Rspec():
+
+    def __init__(self, xml = None, xsd = None):
+        self.xsd = xsd # schema
+        self.rootNode = None # root of the dom
+        self.dict = {} # dict of the rspec.
+        if xml: 
+            if type(xml) == file:
+                self.parseFile(xml)
+            if type(xml) == str:
+                self.parseString(xml)
+            self.dict = self.toDict() 
+  
+    def _getText(self, nodelist):
+        rc = ""
+        for node in nodelist:
+            if node.nodeType == node.TEXT_NODE:
+                rc = rc + node.data
+        return rc
+  
+    # The rspec is comprised of 2 parts, and 1 reference:
+    # attributes/elements describe individual resources
+    # complexTypes are used to describe a set of attributes/elements
+    # complexTypes can include a reference to other complexTypes.
+  
+  
+    def _getName(self, node):
+        '''Gets name of node. If tag has no name, then return tag's localName'''
+        name = None
+        if not node.nodeName.startswith("#"):
+            if node.localName:
+                name = node.localName
+            elif node.attributes.has_key("name"):
+                name = node.attributes.get("name").value
+        return name     
+    # Attribute.  {name : nameofattribute, {items: values})
+    def _attributeDict(self, attributeDom):
+        '''Traverse single attribute node.  Create a dict {attributename : {name: value,}]}'''
+        node = {} # parsed dict
+        for attr in attributeDom.attributes.keys():
+            node[attr] = attributeDom.attributes.get(attr).value
+        return node
+  
+    def toDict(self, nodeDom = None):
+        """
+        convert this rspec to a dict and return it.
+        """
+        node = {}
+        if not nodeDom:
+             nodeDom = self.rootNode
+  
+        elementName = nodeDom.nodeName
+        if elementName and not elementName.startswith("#"):
+            # attributes have tags and values.  get {tag: value}, else {type: value}
+            node[elementName] = self._attributeDict(nodeDom)
+            #node.update(self._attributeDict(nodeDom))
+            # resolve the child nodes.
+            if nodeDom.hasChildNodes():
+                for child in nodeDom.childNodes:
+                    childName = self._getName(child)
+                    if not childName:
+                        continue
+                    if not node[elementName].has_key(childName):
+                        node[elementName][childName] = []       
+                        #node[childName] = []
+                    childdict = self.toDict(child)
+                    for value in childdict.values():
+                        node[elementName][childName].append(value)
+                    #node[childName].append(self.toDict(child))
+        return node
+
+  
+    def toxml(self):
+        """
+        convert this rspec to an xml string and return it.
+        """
+        return self.rootNode.toxml()
+
+  
+    def toprettyxml(self):
+        """
+        print this rspec in xml in a pretty format.
+        """
+        return self.rootNode.toprettyxml()
+
+  
+    def parseFile(self, filename):
+        """
+        read a local xml file and store it as a dom object.
+        """
+        dom = minidom.parse(filename)
+        self.rootNode = dom.childNodes[0]
+  
+  
+    def parseString(self, xml):
+        """
+        read an xml string and store it as a dom object.
+        """
+        xml = xml.replace('\n', '').replace('\t', '').strip()
+        dom = minidom.parseString(xml)
+        self.rootNode = dom.childNodes[0]
+
+    def dict2dom(self, rdict, include_doc = False):
+        """
+        convert a dict object into a dom object.
+        """
+     
+    def elementNode(tagname, rd):
+        element = minidom.Element(tagname)   
+        for key in rd.keys():
+            if isinstance(rd[key], StringTypes):
+                element.setAttribute(key, rd[key])
+            elif isinstance(rd[key], dict):
+                 child = elementNode(key, rd[key])
+                 element.appendChild(child)
+            elif isinstance(rd[key], list):
+                 for item in rd[key]:
+                     child = elementNode(key, item)
+                     element.appendChild(child)
+        return element
+                     
+        node = elementNode(rdict.keys()[0], rdict.values()[0])
+        if include_doc:
+            rootNode = minidom.Document()
+            rootNode.appendChild(node)
+        else:
+            rootNode = node
+        return rootNode
+
+    def parseDict(self, rdict, include_doc = True):
+        """
+        Convert a dictionary into a dom object and store it.
+        """
+        self.rootNode = self.dict2dom(rdict, include_doc)
+    def getDictsByTagName(self, tagname, dom = None):
+        """
+        Search the dom for all elements with the specified tagname
+        and return them as a list of dicts
+        """
+        if not dom:
+            dom = self.rootNode
+        dicts = []
+        doms = dom.getElementsByTagName(tagname)
+        dictlist = [self.toDict(d) for d in doms]
+        for item in dictlist:
+            for value in item.values():
+                dicts.append(value)
+        return dicts
+
+# vim:ts=4:expandtab
diff --git a/geni/util/trustedroot.py b/geni/util/trustedroot.py
new file mode 100644 (file)
index 0000000..e957655
--- /dev/null
@@ -0,0 +1,33 @@
+import os
+
+from gid import *
+
+class TrustedRootList():
+    def __init__(self, dir="./trusted_roots"):
+        self.basedir = dir
+
+        # create the directory to hold the files
+        try:
+            os.makedirs(self.basedir)\r
+        # if the path already exists then pass\r
+        except OSError, (errno, strerr):\r
+            if errno == 17:\r
+                pass
+
+    def add_gid(self, gid):
+        fn = os.path.join(self.basedir, gid.get_hrn() + ".gid")
+
+        gid.save_to_file(fn)
+
+    def get_list(self):
+        gid_list = []
+
+        file_list = os.listdir(self.basedir)
+        for gid_file in file_list:
+            fn = os.path.join(self.basedir, gid_file)
+            if os.path.isfile(fn):
+                gid = GID(filename = fn)
+                gid_list.append(gid)
+
+        return gid_list
+
diff --git a/geni/util/util.py b/geni/util/util.py
new file mode 100644 (file)
index 0000000..f18fdac
--- /dev/null
@@ -0,0 +1,69 @@
+from excep import *
+SR_SUFFIX = '_srr'
+CR_SUFFIX = '_crr'
+
+global_sr_tree = None
+global_cr_tree = None
+
+def set_tree_globals(tree1, tree2):
+    global global_sr_tree
+    global global_cr_tree
+    global_sr_tree = tree1
+    global_cr_tree = tree2
+
+def get_tree_globals():
+    return (global_sr_tree, global_cr_tree)
+    
+#function converts a hierarchical name from geni format to array of strings
+def geni_to_arr(name):        
+    arrayName = []
+    try:
+        parts = name.split(".")
+        for i in range(len(parts)):
+            arrayName.append(parts[i])
+        return arrayName
+    except:
+        raise MalformedHrnException(name)
+
+#used to parse the function name and the parameters specified in "operation_request"
+def msg_to_params(str):        
+    try:
+        return eval(str)
+    except:
+        raise InvalidRPCParams(str)
+
+#returns the authority hrn of a given 'hrn'
+def obtain_authority(hrn):
+       parts = hrn.split(".")
+       auth_str = ''
+       if len(parts) > 1:
+               auth_str = parts[0]+''
+               for i in range(1, len(parts)-1):
+                       auth_str = auth_str + '.' + parts[i]
+       return auth_str
+
+#returns the last element of an hrn
+def get_leaf(hrn):
+    parts = hrn.split(".")
+    return parts[len(parts)-1]
+
+#checks whether the 'hrn_auth' is an authority of 'hrn'
+def check_authority(hrn, hrn_auth):
+    arr = geni_to_arr(hrn)
+    arr_auth = geni_to_arr(hrn_auth)
+    try:
+        for i in range(len(arr_auth)):
+            if arr[i] != arr_auth[i]:
+                return False
+    except: 
+        return False
+    return True
+    
+def hrn_to_tablename(hrn,type):
+    hrn = hrn.replace(".","$")
+    if type == 'slc':
+        hrn = hrn + SR_SUFFIX
+    else:
+        hrn = hrn + CR_SUFFIX
+    return  hrn
+