diff options
-rw-r--r-- | cobbler/cobblerd.py | 32 | ||||
-rw-r--r-- | cobbler/demo_connect.py | 43 | ||||
-rw-r--r-- | cobbler/remote.py | 29 | ||||
-rw-r--r-- | cobbler/server/__init__.py | 0 | ||||
-rw-r--r-- | cobbler/server/xmlrpcclient.py | 72 | ||||
-rw-r--r-- | cobbler/server/xmlrpclib2.py | 236 | ||||
-rw-r--r-- | setup.py | 1 |
7 files changed, 365 insertions, 48 deletions
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py index 6edfc8d..4e9e52e 100644 --- a/cobbler/cobblerd.py +++ b/cobbler/cobblerd.py @@ -19,6 +19,7 @@ import glob from rhpl.translate import _, N_, textdomain, utf8 import xmlrpclib +from server import xmlrpclib2 import api as cobbler_api import yaml # Howell Clark version import utils @@ -50,12 +51,19 @@ def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger): if str(settings.xmlrpc_rw_enabled) != "0": pid2 = os.fork() if pid2 == 0: - do_xmlrpc(bootapi, settings, xmlrpc_port, logger) + do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger) else: do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger) else: logger.debug("xmlrpc_rw is disabled in the settings file") + do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger) + +def do_mandatory_xmlrpc_tasks(bootapi,settings,xmlrpc_port,logger): + pid3 = os.fork() + if pid3 == 0: do_xmlrpc(bootapi, settings, xmlrpc_port, logger) + else: + do_xmlrpc_unix(bootapi, settings, logger) def do_other_tasks(bootapi, settings, syslog_port, logger): @@ -96,7 +104,7 @@ def do_xmlrpc(bootapi, settings, port, logger): # This is the simple XMLRPC API we provide to koan and other # apps that do not need to manage Cobbler's config - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface) + xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,True) server = remote.CobblerXMLRPCServer(('', port)) server.logRequests = 0 # don't print stuff @@ -112,7 +120,7 @@ def do_xmlrpc(bootapi, settings, port, logger): def do_xmlrpc_rw(bootapi,settings,port,logger): - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface) + xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,False) server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port)) server.logRequests = 0 # don't print stuff logger.debug("XMLRPC (read-write variant) running on %s" % port) @@ -125,6 +133,22 @@ def do_xmlrpc_rw(bootapi,settings,port,logger): # interrupted? try to serve again time.sleep(0.5) +def do_xmlrpc_unix(bootapi,settings,logger): + + xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True) + SOCKT = "/var/lib/cobbler/sock" + server = xmlrpclib2.UnixXMLRPCServer(SOCKT) + server.logRequests = 0 # don't print stuff + logger.debug("XMLRPC (socket variant) available on %s" % SOCKT) + server.register_instance(xinterface) + + while True: + try: + server.serve_forever() + except IOError: + # interrupted? try to serve again + time.sleep(0.5) + def do_syslog(bootapi, settings, port, logger): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -173,5 +197,5 @@ if __name__ == "__main__": xmlrpc_port = settings.xmlrpc_port xmlrpc_port2 = settings.xmlrpc_rw_port logger = bootapi.logger_remote - do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger) + do_xmlrpc_unix(bootapi, settings, logger) diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py index 8a91012..0fa058b 100644 --- a/cobbler/demo_connect.py +++ b/cobbler/demo_connect.py @@ -1,15 +1,7 @@ #!/usr/bin/python """ -work in progress: demo connection code for Cobbler read-write API -uses SSL+XMLRPC (though just XMLRPC will still work) -adapted from Virt-Factory's old vf_nodecomm source -XMLRPCSSL portions based on http://linux.duke.edu/~icon/misc/xmlrpcssl.py - Copyright 2007, Red Hat, Inc -Michael DeHaan <mdehaan@redhat.com> -Adrian Likins <alikins@redhat.com> -Scott Seago <sseago@redhat.com> This software may be freely redistributed under the terms of the GNU general public license. @@ -19,40 +11,11 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ -import sys -import socket - -from M2Crypto import SSL -from M2Crypto.m2xmlrpclib import SSL_Transport, Server -from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler - - -# workaround for bz #237902 -class CobblerTransport(SSL_Transport): - def __init__(self, ssl_context=None, use_datetime=0): - self._use_datetime = use_datetime - SSL_Transport.__init__(self,ssl_context=ssl_context) - -def demo_connect(username,password,server): - my_ctx = SSL.Context('sslv23') - # my_ctx.load_client_ca("foo.pem") - # my_ctx.load_cert("bar.pem","baz.pem") - my_ctx.set_session_id_ctx("xmlrpcssl") - my_ctx.set_allow_unknown_ca(True) - my_ctx.set_verify(0,-1) # full anonymous (we hope) - my_uri = "https://%s:443/cobbler_api_rw" % server - print "connecting to: %s" % my_uri - my_rserver = Server(my_uri, CobblerTransport(ssl_context = my_ctx)) - token = my_rserver.login(username,password) - print "got a token: %s" % token - rc = my_rserver.test(token) - print "got test results: %s" % rc +from server.xmlrpcclient import ServerProxy if __name__ == "__main__": - USERNAME = "mdehaan" - PASSWORD = "llamas2007" - SERVER = "mdehaan.rdu.redhat.com" - demo_connect(USERNAME,PASSWORD,SERVER) + sp = ServerProxy("httpu:///var/lib/cobbler/sock") + print sp.login("<system>","") diff --git a/cobbler/remote.py b/cobbler/remote.py index 07539e4..898e712 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -53,9 +53,10 @@ class CobblerXMLRPCInterface: interface are intentionally /not/ validated. It's a public API. """ - def __init__(self,api,logger): + def __init__(self,api,logger,enable_auth_if_relevant): self.api = api self.logger = logger + self.auth_enabled = enable_auth_if_relevant def __sorter(self,a,b): return cmp(a["name"],b["name"]) @@ -427,9 +428,9 @@ class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): class ProxiedXMLRPCInterface: - def __init__(self,api,logger,proxy_class): + def __init__(self,api,logger,proxy_class,enable_auth_if_relevant=True): self.logger = logger - self.proxied = proxy_class(api,logger) + self.proxied = proxy_class(api,logger,enable_auth_if_relevant) def _dispatch(self, method, params): @@ -449,8 +450,9 @@ class ProxiedXMLRPCInterface: class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): - def __init__(self,api,logger): + def __init__(self,api,logger,enable_auth_if_relevant): self.api = api + self.auth_enabled = enable_auth_if_relevant self.logger = logger self.token_cache = TOKEN_CACHE self.object_cache = OBJECT_CACHE @@ -511,9 +513,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Returns whether this user/pass combo should be given access to the cobbler read-write API. + For the system user, this answer is always "yes", but + it is only valid for the socket interface. + FIXME: currently looks for users in /etc/cobbler/auth.conf Would be very nice to allow for PAM and/or just Kerberos. """ + if not self.auth_enabled and input_user == "<system>": + return True + if self.auth_enabled and input_user == "<system>": + return False return self.api.authenticate(input_user,input_password) def __validate_token(self,token): @@ -527,8 +536,18 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ self.__invalidate_expired_tokens() self.__invalidate_expired_objects() + + if not self.auth_enabled: + user = self.get_user_from_token(token) + if user == "<system>": + self.token_cache[token] = (time.time(), user) # update to prevent timeout + return True + if self.token_cache.has_key(token): user = self.get_user_from_token(token) + if user == "<system>": + # system token is only valid over Unix socket + return False self.token_cache[token] = (time.time(), user) # update to prevent timeout return True else: @@ -537,6 +556,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def check_access(self,token,resource,arg1=None,arg2=None): validated = self.__validate_token(token) + if not self.auth_enabled: + return True return self.__authorize(token,resource,arg1,arg2) diff --git a/cobbler/server/__init__.py b/cobbler/server/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cobbler/server/__init__.py diff --git a/cobbler/server/xmlrpcclient.py b/cobbler/server/xmlrpcclient.py new file mode 100644 index 0000000..cc0687c --- /dev/null +++ b/cobbler/server/xmlrpcclient.py @@ -0,0 +1,72 @@ +#============================================================================ +# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU Lesser General Public +# License as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#============================================================================ +# Copyright (C) 2006 Anthony Liguori <aliguori@us.ibm.com> +# Copyright (C) 2007 XenSource Inc. +# Copyright (C) 2007 Red Hat Inc, Michael DeHaan <mdehaan@redhat.com> + +from httplib import FakeSocket, HTTPConnection, HTTP +import socket +import string +import xmlrpclib +from types import StringTypes + +class HTTPUnixConnection(HTTPConnection): + def connect(self): + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(self.host) + +class HTTPUnix(HTTP): + _connection_class = HTTPUnixConnection + +class UnixTransport(xmlrpclib.Transport): + def request(self, host, handler, request_body, verbose=0): + self.__handler = handler + return xmlrpclib.Transport.request(self, host, '/RPC2', + request_body, verbose) + def make_connection(self, host): + return HTTPUnix(self.__handler) + +# See xmlrpclib2.TCPXMLRPCServer._marshalled_dispatch. +def conv_string(x): + if isinstance(x, StringTypes): + s = string.replace(x, "'", r"\047") + exec "s = '" + s + "'" + return s + else: + return x + + +class ServerProxy(xmlrpclib.ServerProxy): + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1): + if transport == None: + (protocol, rest) = uri.split(':', 1) + if protocol == 'httpu': + uri = 'http:' + rest + transport = UnixTransport() + else: + raise ValueError("only httpu://path/to/socket is supported") + xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding, + verbose, allow_none) + + + def __request(self, methodname, params): + response = xmlrpclib.ServerProxy.__request(self, methodname, params) + + if isinstance(response, tuple): + return tuple([conv_string(x) for x in response]) + else: + return conv_string(response) + diff --git a/cobbler/server/xmlrpclib2.py b/cobbler/server/xmlrpclib2.py new file mode 100644 index 0000000..d329241 --- /dev/null +++ b/cobbler/server/xmlrpclib2.py @@ -0,0 +1,236 @@ +#============================================================================ +# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU Lesser General Public +# License as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#============================================================================ +# Copyright (C) 2006 Anthony Liguori <aliguori@us.ibm.com> +# Copyright (C) 2006 XenSource Inc. +# Copyright (C) 2007 Red Hat Inc., Michael DeHaan <mdehaan@redhat.com> +#============================================================================ + +""" +An enhanced XML-RPC client/server interface for Python. +""" + +import re +import fcntl +from types import * +import os +import errno +import traceback + +from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler +import SocketServer +import xmlrpclib, socket, os, stat + +#import mkdir + +# +# Convert all integers to strings as described in the Xen API +# + + +def stringify(value): + if isinstance(value, long) or \ + (isinstance(value, int) and not isinstance(value, bool)): + return str(value) + elif isinstance(value, dict): + new_value = {} + for k, v in value.items(): + new_value[stringify(k)] = stringify(v) + return new_value + elif isinstance(value, (tuple, list)): + return [stringify(v) for v in value] + else: + return value + + +# We're forced to subclass the RequestHandler class so that we can work around +# some bugs in Keep-Alive handling and also enabled it by default +class XMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + protocol_version = "HTTP/1.1" + + def __init__(self, request, client_address, server): + SimpleXMLRPCRequestHandler.__init__(self, request, client_address, + server) + + # this is inspired by SimpleXMLRPCRequestHandler's do_POST but differs + # in a few non-trivial ways + # 1) we never generate internal server errors. We let the exception + # propagate so that it shows up in the Xend debug logs + # 2) we don't bother checking for a _dispatch function since we don't + # use one + def do_POST(self): + addrport = self.client_address + #if not connection.hostAllowed(addrport, self.hosts_allowed): + # self.connection.shutdown(1) + # return + + data = self.rfile.read(int(self.headers["content-length"])) + rsp = self.server._marshaled_dispatch(data) + + self.send_response(200) + self.send_header("Content-Type", "text/xml") + self.send_header("Content-Length", str(len(rsp))) + self.end_headers() + + self.wfile.write(rsp) + self.wfile.flush() + #if self.close_connection == 1: + # self.connection.shutdown(1) + +def parents(dir, perms, enforcePermissions = False): + """ + Ensure that the given directory exists, creating it if necessary, but not + complaining if it's already there. + + @param dir The directory name. + @param perms One of the stat.S_ constants. + @param enforcePermissions Enforce our ownership and the given permissions, + even if the directory pre-existed with different ones. + """ + # Catch the exception here, rather than checking for the directory's + # existence first, to avoid races. + try: + os.makedirs(dir, perms) + except OSError, exn: + if exn.args[0] != errno.EEXIST or not os.path.isdir(dir): + raise + if enforcePermissions: + os.chown(dir, os.geteuid(), os.getegid()) + os.chmod(dir, stat.S_IRWXU) + + +# This is a base XML-RPC server for TCP. It sets allow_reuse_address to +# true, and has an improved marshaller that logs and serializes exceptions. + +class TCPXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer): + allow_reuse_address = True + + def __init__(self, addr, requestHandler=None, + logRequests = 1): + if requestHandler is None: + requestHandler = XMLRPCRequestHandler + SimpleXMLRPCServer.__init__(self, addr, + (lambda x, y, z: + requestHandler(x, y, z)), + logRequests) + + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + + def get_request(self): + (client, addr) = SimpleXMLRPCServer.get_request(self) + flags = fcntl.fcntl(client.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(client.fileno(), fcntl.F_SETFD, flags) + return (client, addr) + + def _marshaled_dispatch(self, data, dispatch_method = None): + params, method = xmlrpclib.loads(data) + if False: + # Enable this block of code to exit immediately without sending + # a response. This allows you to test client-side crash handling. + import sys + sys.exit(1) + try: + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + + if (response is None or + not isinstance(response, dict) or + 'Status' not in response): + #log.exception('Internal error handling %s: Invalid result %s', + # method, response) + response = { "Status": "Failure", + "ErrorDescription": + ['INTERNAL_ERROR', + 'Invalid result %s handling %s' % + (response, method)]} + + # With either Unicode or normal strings, we can only transmit + # \t, \n, \r, \u0020-\ud7ff, \ue000-\ufffd, and \u10000-\u10ffff + # in an XML document. xmlrpclib does not escape these values + # properly, and then breaks when it comes to parse the document. + # To hack around this problem, we use repr here and exec above + # to transmit the string using Python encoding. + # Thanks to David Mertz <mertz@gnosis.cx> for the trick (buried + # in xml_pickle.py). + if isinstance(response, StringTypes): + response = repr(response)[1:-1] + + response = (response,) + response = xmlrpclib.dumps(response, + methodresponse=1, + allow_none=1) + except Exception, exn: + try: + #if self.xenapi: + # if _is_not_supported(exn): + # errdesc = ['MESSAGE_METHOD_UNKNOWN', method] + # else: + # #log.exception('Internal error handling %s', method) + # errdesc = ['INTERNAL_ERROR', str(exn)] + # + # response = xmlrpclib.dumps( + # ({ "Status": "Failure", + # "ErrorDescription": errdesc },), + # methodresponse = 1) + #else: + # import xen.xend.XendClient + if isinstance(exn, xmlrpclib.Fault): + response = xmlrpclib.dumps(exn) + else: + # log.exception('Internal error handling %s', method) + response = xmlrpclib.dumps( + xmlrpclib.Fault(101, str(exn))) + except Exception, exn2: + # FIXME + traceback.print_exc() + + return response + + +notSupportedRE = re.compile(r'method "(.*)" is not supported') +def _is_not_supported(exn): + try: + m = notSupportedRE.search(exn[0]) + return m is not None + except: + return False + + +# This is a XML-RPC server that sits on a Unix domain socket. +# It implements proper support for allow_reuse_address by +# unlink()'ing an existing socket. + +class UnixXMLRPCRequestHandler(XMLRPCRequestHandler): + def address_string(self): + try: + return XMLRPCRequestHandler.address_string(self) + except ValueError, e: + return self.client_address[:2] + +class UnixXMLRPCServer(TCPXMLRPCServer): + address_family = socket.AF_UNIX + allow_address_reuse = True + + def __init__(self, addr, logRequests = 1): + parents(os.path.dirname(addr), stat.S_IRWXU, True) + if self.allow_reuse_address and os.path.exists(addr): + os.unlink(addr) + + TCPXMLRPCServer.__init__(self, addr, + UnixXMLRPCRequestHandler, logRequests) @@ -57,6 +57,7 @@ if __name__ == "__main__": "cobbler", "cobbler/yaml", "cobbler/modules", + "cobbler/server", "cobbler/webui", ], scripts = ["scripts/cobbler", "scripts/cobblerd", "scripts/cobbler_auth_help"], |