#!/usr/bin/python
# Copyright (c) 2009, Purdue University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# Neither the name of the Purdue University nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Server library for XML RPC
Allows client to connect and run arbitrary functions in core.py.
"""
__copyright__ = 'Copyright (C) 2009, Purdue University'
__license__ = 'BSD'
__version__ = '#TRUNK#'
import datetime
import os
import inspect
import SocketServer
import StringIO
import time
import traceback
import uuid
import xmlrpclib
import roster_core
import credentials
from ssl_xml_rpc_lib import SecureXMLRPCServer
from ssl_xml_rpc_lib import SecureXMLRpcRequestHandler
roster_core.core.CheckCoreVersionMatches(__version__)
[docs]class ArgumentError(roster_core.CoreError):
pass
[docs]class FunctionError(roster_core.CoreError):
pass
[docs]class ServerError(roster_core.CoreError):
pass
[docs]class ThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SecureXMLRPCServer):
pass
[docs]class Server(object):
"""Daemon library used to serve commands to the client."""
def __init__(self, config_instance, keyfile=None, certfile=None,
inf_renew_time=None, core_die_time=None,
clean_time=None, unittest_timestamp=None,
unit_test=False):
"""Sets up config instance. Stores core instances.
Inputs:
config_instance: instance of Config
keyfile: key file used for ssl
certfile: cert file used for ssl
inf_renew_time: time to refresh infinite credentials (seconds)
core_die_time: time for each core instance to die (seconds)
clean_time: time to wait between core instance cleanings (seconds)
unit_test: boolean indicating a unit-test is being run.
"""
self.config_instance = config_instance
self.keyfile = keyfile
if( keyfile is None ):
self.keyfile = self.config_instance.config_file['server'][
'ssl_key_file']
self.certfile = certfile
if( certfile is None ):
self.certfile = self.config_instance.config_file['server'][
'ssl_cert_file']
self.inf_renew_time = inf_renew_time
self.port = self.config_instance.config_file['server'][
'port']
self.server_name = self.config_instance.config_file['server'][
'host']
self.core_store_cleanup_running = False
if( inf_renew_time is None ):
self.inf_renew_time = self.config_instance.config_file['server'][
'inf_renew_time']
self.core_die_time = core_die_time
if( core_die_time is None ):
self.core_die_time = self.config_instance.config_file['server'][
'core_die_time']
self.log_file = self.config_instance.config_file['server'][
'server_log_file']
self.get_credentials_wait_increment = self.config_instance.config_file[
'server']['get_credentials_wait_increment']
self.server_killswitch = self.config_instance.config_file['server'][
'server_killswitch']
self.clean_time = clean_time
if( clean_time is None ):
self.clean_time = self.core_die_time
self.cred_cache_instance = credentials.CredCache(self.config_instance,
self.inf_renew_time, unit_test)
self.unittest_timestamp = unittest_timestamp
self.core_store = [] # {'user': user, 'last_used': last_used, 'instance': }
self.get_credentials_wait = {} # {'user1': 3, 'user2': 4}
self.last_cleaned = datetime.datetime.now()
self.LogMessage('Roster server started on port %s' % self.port, 'rosterd')
[docs] def LogException(self, function, args, kwargs, user_name):
"""Save functions traceback to logfile
Inputs:
function: string of function name
args: args list
kwargs: keyword args dict
user_name: username string
Outputs:
str: uuid string from logfile
"""
uuid_string = str(uuid.uuid4())
log_file_contents = []
exception_string = StringIO.StringIO()
traceback.print_exc(None, exception_string)
log_file_contents.append('\n\n---------------------\n')
log_file_contents.append(uuid_string)
log_file_contents.append('\n')
log_file_contents.append('FUNCTION: %s\n' % function)
log_file_contents.append('ARGS: %s\n' % args)
log_file_contents.append('KWARGS: %s\n' % kwargs)
log_file_contents.append('USER: %s\n' % user_name)
log_file_contents.append('TIMESTAMP: %s\n\n' % (
datetime.datetime.now().isoformat()))
log_file_contents.append(exception_string.getvalue())
log_file_contents.append('\n---------------------\n')
log_file_handle = open(self.log_file, 'a')
try:
log_file_handle.writelines(''.join(log_file_contents))
finally:
log_file_handle.close()
return uuid_string
[docs] def LogMessage(self, log_message, user_name):
"""Save a message to the logfile
Inputs:
log_message: string of the log message
user_name: username string
Outputs:
str: uuid string from logfile
"""
uuid_string = str(uuid.uuid4())
log_file_contents = []
log_file_contents.append('\n\n---------------------\n')
log_file_contents.append(uuid_string)
log_file_contents.append('\n')
log_file_contents.append('MESSAGE: %s\n' % log_message)
log_file_contents.append('USER: %s\n' % user_name)
log_file_contents.append('TIMESTAMP: %s' % (
datetime.datetime.now().isoformat()))
log_file_contents.append('\n---------------------\n')
log_file_handle = open(self.log_file, 'a')
try:
log_file_handle.writelines(''.join(log_file_contents))
finally:
log_file_handle.close()
return uuid_string
[docs] def CleanupCoreStore(self):
"""Cleans up expired instances in core_store"""
delete_list = []
if( self.last_cleaned + datetime.timedelta(seconds=self.clean_time) < (
datetime.datetime.now()) ):
self.last_cleaned = datetime.datetime.now()
for core_instance in self.core_store:
if( core_instance['last_used'] + datetime.timedelta(
seconds=self.core_die_time) < datetime.datetime.now() ):
delete_list.append(core_instance)
for instance in delete_list:
self.core_store.remove(instance)
[docs] def StringToUnicode(self, object_to_convert):
"""Converts objects recursively into strings.
Inputs:
object_to_convert: the object that needs to be converted to unicode
Outputs:
converted_object: object can vary type, but all strings will be unicode
"""
converted_object = object_to_convert
if( isinstance(object_to_convert, str) ):
converted_object = unicode(object_to_convert)
elif( isinstance(object_to_convert, dict) ):
new_dict = {}
for key, value in object_to_convert.iteritems():
new_dict[unicode(key)] = self.StringToUnicode(value)
converted_object = new_dict
elif( isinstance(object_to_convert, list) ):
for index, item in enumerate(object_to_convert):
object_to_convert[index] = self.StringToUnicode(item)
converted_object = object_to_convert
elif( isinstance(object_to_convert, xmlrpclib.DateTime) ):
converted_object = datetime.datetime.strptime(
object_to_convert.value, "%Y%m%dT%H:%M:%S")
return converted_object
[docs] def GetCoreInstance(self, user_name):
"""Finds core instance in core store, if one cannot be found
it will be created.
Inputs:
user_name: string of user name
Outputs:
instance: instance of dnsmgmtcore
"""
core_instance_dict = {}
for core_instance_dict in self.core_store:
if( core_instance_dict['user_name'] == user_name ):
break
else:
new_core_instance = roster_core.Core(user_name, self.config_instance,
unittest_timestamp=(
self.unittest_timestamp))
core_instance_dict = {'user_name': user_name,
'last_used': datetime.datetime.now(),
'core_instance': new_core_instance}
self.core_store.append(core_instance_dict)
return core_instance_dict['core_instance']
[docs] def CoreRun(self, function, user_name, credfile, args = None, kwargs = None):
"""Runs a function in core_instance with arbitrary parameters
Inputs:
function: name of the function to be run
user_name: user running the function
args: list of arguments to be passed to function
kwargs: dictionary of keyword arguments to be passed to function
Outputs:
dictionary: dictionary of return from function run and new cred string
example: {'core_return': returned_data,
'new_credential': u'
be4d4ecf-d670-44a0-b957-770e118e2755'}
"""
if args is None:
args = []
if kwargs is None:
kwargs = {}
uuid_string = None
credfile = unicode(credfile)
user_name = unicode(user_name)
## Instantiate the core instance
core_instance = self.GetCoreInstance(user_name)
core_helper_instance = roster_core.CoreHelpers(core_instance)
cred_status = self.cred_cache_instance.CheckCredential(credfile, user_name,
core_instance)
if( cred_status is not None ):
## Fix non unicode strings containing no unicode characters
args = self.StringToUnicode(args)
kwargs = self.StringToUnicode(kwargs)
## Fix unicoded kwargs keys
new_kwargs = {}
for key in kwargs:
new_kwargs[str(key)] = kwargs[key]
kwargs = new_kwargs
if( function.startswith('_') ):
raise FunctionError('Function does not exist.')
elif( hasattr(core_instance, function) ):
run_func = getattr(core_instance, function)
elif( hasattr(core_helper_instance, function) ):
run_func = getattr(core_helper_instance, function)
else:
raise FunctionError('Function does not exist.')
types = inspect.getargspec(run_func)
error_class = None
try:
## Figure out what each function is expecting
if( types[3] is None and len(types[0]) == 1 ):
# Nothing in core function
core_return = run_func()
elif( types[3] is None and len(types[0]) != 1 ):
# Arguments only in core function
core_return = run_func(*args)
elif( types[3] is not None and (len(types[0]) - len(types[3])) == 1 ):
# KWArguments only in core function
core_return = run_func(**kwargs)
elif( types[3] is not None and (len(types[0]) - len(types[3])) != 1 ):
# Both kwargs and args in core function
core_return = run_func(*args, **kwargs)
else:
raise ArgumentError('Arguments do not match.')
except roster_core.errors.InternalError, error:
uuid_string = self.LogException(function, args, kwargs, user_name)
error_class = 'InternalError'
except roster_core.errors.UserError, error:
error_class = 'UserError'
except Exception, error:
uuid_string = self.LogException(function, args, kwargs, user_name)
error_class = error.__class__.__name__
if( error_class is not None ):
return {'log_uuid_string': uuid_string, 'error': str(error),
'error_class': error_class, 'core_return': None, 'new_credential': None}
core_return = {'core_return': core_return, 'new_credential': cred_status,
'log_uuid_string': uuid_string, 'error': None}
else:
core_return = 'ERROR: Invalid Credentials'
return core_return
[docs] def GetCredentials(self, user_name, password):
"""Connects to credential cache and gets a credential file.
Inputs:
user_name: string of user name
password: string of password (plaintext)
Outputs:
string: string of credential
example: u'be4d4ecf-d670-44a0-b957-770e118e2755'
"""
error_class = None
try:
user_name = unicode(user_name)
core_instance = self.GetCoreInstance(user_name)
cred_string = self.cred_cache_instance.GetCredentials(user_name, password,
core_instance)
if( cred_string == '' ):
if( not self.get_credentials_wait.has_key(user_name) ):
self.get_credentials_wait.update({user_name: 0})
time.sleep(self.get_credentials_wait[user_name])
self.get_credentials_wait[user_name] = self.get_credentials_wait[
user_name] + self.get_credentials_wait_increment
elif( self.get_credentials_wait.has_key(user_name) ):
self.get_credentials_wait.pop(user_name)
except Exception, error:
uuid_string = self.LogException(
'GetCredentials', [user_name, '<password>'], {}, user_name)
error_class = error.__class__.__name__
if( error_class is not None ):
return {'log_uuid_string': uuid_string, 'error': str(error),
'error_class': error_class, 'core_return': None, 'new_credential': None}
return {'log_uuid_string': uuid_string, 'error': str(error),
'core_return': None, 'new_credential': None}
return cred_string
[docs] def IsAuthenticated(self, user_name, credstring):
"""Checks if string is valid.
Inputs:
credstring: string of credential
Outputs:
bool: bool of valid string
"""
error_class = None
try:
user_name = unicode(user_name)
credstring = unicode(credstring)
core_instance = self.GetCoreInstance(user_name)
valid = self.cred_cache_instance.CheckCredential(
credstring, user_name, core_instance)
except Exception, error:
uuid_string = self.LogException(
'IsAuthenticated', [user_name, '<credstring>'], {}, user_name)
error_class = error.__class__.__name__
if( error_class is not None ):
return {'log_uuid_string': uuid_string, 'error': str(error),
'error_class': error_class, 'core_return': None, 'new_credential': None}
return {'log_uuid_string': uuid_string, 'error': str(error),
'core_return': None, 'new_credential': None}
if( valid == '' ):
return True
return False
[docs] def GetVersion(self):
return __version__
[docs] def Serve(self, server_name=None, port=None):
"""Main server function
Inputs:
server_name: name of server you wish to create
port: listening port number of server
"""
if( not os.path.exists ):
try:
open(self.log_file, 'w').close()
except:
raise ServerError('Could not write to logfile "%s"' % self.log_file)
if( not port ):
port = self.port
if( not server_name ):
server_name = self.server_name
if( self.server_killswitch ):
raise ServerError('"server_killswitch" must be set to "off" in "%s" '
'to allow the XML-RPC server to run.' % (
self.config_instance.config_file_path))
self.server = ThreadedXMLRPCServer((server_name, port),
SecureXMLRpcRequestHandler,
self.keyfile, self.certfile)
self.server.register_function(self.CoreRun)
self.server.register_function(self.GetCredentials)
self.server.register_function(self.IsAuthenticated)
self.server.register_function(self.GetVersion)
try:
while 1:
self.server.handle_request()
if( not self.core_store_cleanup_running ):
self.core_store_cleanup_running = True
self.CleanupCoreStore()
self.core_store_cleanup_running = False
except KeyboardInterrupt:
print "Stopped by user."