diff -u pywbem-0.7.0/debian/changelog pywbem-0.7.0/debian/changelog --- pywbem-0.7.0/debian/changelog +++ pywbem-0.7.0/debian/changelog @@ -1,3 +1,11 @@ +pywbem (0.7.0-4ubuntu1~14.10.1) utopic; urgency=medium + + * Add CA Certificate verification from upstream + Import commits r624, r625, r627 and r628 from upstream + to implement CA Certificate verification (LP: #1385469) + + -- Louis Bouchard Tue, 25 Nov 2014 12:40:30 +0100 + pywbem (0.7.0-4) unstable; urgency=low * Remove embedded code copy from python-ply from the diff -u pywbem-0.7.0/debian/control pywbem-0.7.0/debian/control --- pywbem-0.7.0/debian/control +++ pywbem-0.7.0/debian/control @@ -1,10 +1,10 @@ Source: pywbem Section: python Priority: extra -Maintainer: Debian Python Modules Team -Uploaders: Bernd Zeimetz +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Python Modules Team Build-Depends: debhelper (>= 7), python-all (>= 2.5.4-1~), dpatch -Build-Depends-Indep: python-support (>= 0.4), docbook-xsl, xsltproc, python-ply +Build-Depends-Indep: python-support (>= 0.4), docbook-xsl, xsltproc, python-ply, python-m2crypto Vcs-Svn: svn://svn.debian.org/python-modules/packages/pywbem/trunk/ Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/pywbem/trunk/ Homepage: http://pywbem.sourceforge.net/ @@ -12,7 +12,7 @@ Package: python-pywbem Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-ply +Depends: ${python:Depends}, ${misc:Depends}, python-ply, python-m2crypto Recommends: python-twisted-web, python-twisted-core, python-elementtree | python (>= 2.5) Description: Python WBEM Client and Provider Interface diff -u pywbem-0.7.0/debian/patches/00list pywbem-0.7.0/debian/patches/00list --- pywbem-0.7.0/debian/patches/00list +++ pywbem-0.7.0/debian/patches/00list @@ -1,0 +2 @@ +add_ssl_verify_host.patch.dpatch only in patch2: unchanged: --- pywbem-0.7.0.orig/debian/patches/add_ssl_verify_host.patch.dpatch +++ pywbem-0.7.0/debian/patches/add_ssl_verify_host.patch.dpatch @@ -0,0 +1,1573 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## add_ssl_verify_host.patch.dpatch by Louis Bouchard > +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Description: Add CA Certificate verification from upstream +## DP: +## DP: Import commits r624, r625, r627 and r628 from upstream to implement +## DP: CA Certificate verification +## DP: Origin: http://sourceforge.net/p/pywbem/code/HEAD/tree/ +## DP: Author: Louis Bouchard +## DP: Bug-Ubuntu: http://bugs.launchpad.net/bugs/1385469 + +@DPATCH@ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' pywbem-0.7.0~/cim_http.py pywbem-0.7.0/cim_http.py +--- pywbem-0.7.0~/cim_http.py 2008-11-06 02:01:51.000000000 +0100 ++++ pywbem-0.7.0/cim_http.py 2014-11-25 11:52:29.213382687 +0100 +@@ -29,6 +29,7 @@ + data and interpret the result. + ''' + ++from M2Crypto import SSL, Err + import sys, string, re, os, socket, pwd + from stat import S_ISSOCK + import cim_obj +@@ -59,6 +60,15 @@ + if m: + host = url[len(m.group(0)):] + ++ # IPv6 with/without port ++ m = re.match("^\[?([0-9A-Fa-f:]*)\]?(:([0-9]*))?$", host) ++ if m: ++ host = m.group(1) ++ port_tmp = m.group(3) ++ if port_tmp: ++ port = int(port_tmp) ++ return host, port, ssl ++ + s = string.split(host, ":") # Set port number + if len(s) != 1: + host = s[0] +@@ -66,8 +76,26 @@ + + return host, port, ssl + ++def get_default_ca_certs(): ++ """ ++ Try to find out system path with ca certificates. This path is cached and ++ returned. If no path is found out, None is returned. ++ """ ++ if not hasattr(get_default_ca_certs, '_path'): ++ for path in ( ++ '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt', ++ '/etc/ssl/certs', ++ '/etc/ssl/certificates'): ++ if os.path.exists(path): ++ get_default_ca_certs._path = path ++ break ++ else: ++ get_default_ca_certs._path = None ++ return get_default_ca_certs._path ++ + def wbem_request(url, data, creds, headers = [], debug = 0, x509 = None, +- verify_callback = None): ++ verify_callback = None, ca_certs = None, ++ no_verification = False): + """Send XML data over HTTP to the specified url. Return the + response in XML. Uses Python's build-in httplib. x509 may be a + dictionary containing the location of the SSL certificate and key +@@ -97,10 +125,49 @@ + + class HTTPSConnection(HTTPBaseConnection, httplib.HTTPSConnection): + def __init__(self, host, port=None, key_file=None, cert_file=None, +- strict=None): ++ strict=None, ca_certs=None, verify_callback=None): + httplib.HTTPSConnection.__init__(self, host, port, key_file, + cert_file, strict) +- ++ self.ca_certs = ca_certs ++ self.verify_callback = verify_callback ++ ++ def connect(self): ++ "Connect to a host on a given (SSL) port." ++ self.sock = socket.create_connection((self.host, self.port), ++ self.timeout, self.source_address) ++ if self._tunnel_host: ++ self.sock = sock ++ self._tunnel() ++ ctx = SSL.Context('sslv23') ++ if self.cert_file: ++ ctx.load_cert(self.cert_file, keyfile=self.key_file) ++ if self.ca_certs: ++ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, ++ depth=9, callback=verify_callback) ++ if os.path.isdir(self.ca_certs): ++ ctx.load_verify_locations(capath=self.ca_certs) ++ else: ++ ctx.load_verify_locations(cafile=self.ca_certs) ++ try: ++ self.sock = SSL.Connection(ctx, self.sock) ++ # Below is a body of SSL.Connection.connect() method ++ # except for the first line (socket connection). We want to preserve ++ # tunneling ability. ++ self.sock.addr = (self.host, self.port) ++ self.sock.setup_ssl() ++ self.sock.set_connect_state() ++ ret = self.sock.connect_ssl() ++ if self.ca_certs: ++ check = getattr(self.sock, 'postConnectionCheck', ++ self.sock.clientPostConnectionCheck) ++ if check is not None: ++ if not check(self.sock.get_peer_cert(), self.host): ++ raise Error('SSL error: post connection check failed') ++ return ret ++ except ( Err.SSLError, SSL.SSLError, SSL.SSLTimeoutError ++ , SSL.Checker.WrongHost), arg: ++ raise Error("SSL error: %s" % arg) ++ + class FileHTTPConnection(HTTPBaseConnection, httplib.HTTPConnection): + def __init__(self, uds_path): + httplib.HTTPConnection.__init__(self, 'localhost') +@@ -109,47 +176,36 @@ + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(self.uds_path) + +- host, port, ssl = parse_url(url) ++ host, port, use_ssl = parse_url(url) + + key_file = None + cert_file = None + +- if ssl: +- +- if x509 is not None: +- cert_file = x509.get('cert_file') +- key_file = x509.get('key_file') +- +- if verify_callback is not None: +- try: +- from OpenSSL import SSL +- ctx = SSL.Context(SSL.SSLv3_METHOD) +- ctx.set_verify(SSL.VERIFY_PEER, verify_callback) +- # Add the key and certificate to the session +- if cert_file is not None and key_file is not None: +- ctx.use_certificate_file(cert_file) +- ctx.use_privatekey_file(key_file) +- s = SSL.Connection(ctx, socket.socket(socket.AF_INET, +- socket.SOCK_STREAM)) +- s.connect((host, port)) +- s.do_handshake() +- s.shutdown() +- s.close() +- except socket.error, arg: +- raise Error("Socket error: %s" % (arg,)) +- except socket.sslerror, arg: +- raise Error("SSL error: %s" % (arg,)) ++ if use_ssl and x509 is not None: ++ cert_file = x509.get('cert_file') ++ key_file = x509.get('key_file') + + numTries = 0 + localAuthHeader = None + tryLimit = 5 + ++ if isinstance(data, unicode): ++ data = data.encode('utf-8') + data = '\n' + data + ++ if not no_verification and ca_certs is None: ++ ca_certs = get_default_ca_certs() ++ elif no_verification: ++ ca_certs = None ++ + local = False +- if ssl: +- h = HTTPSConnection(host, port = port, key_file = key_file, +- cert_file = cert_file) ++ if use_ssl: ++ h = HTTPSConnection(host, ++ port = port, ++ key_file = key_file, ++ cert_file = cert_file, ++ ca_certs = ca_certs, ++ verify_callback = verify_callback) + else: + if url.startswith('http'): + h = HTTPConnection(host, port = port) +@@ -167,7 +223,7 @@ + raise Error('Invalid URL') + + locallogin = None +- if host in ('localhost', '127.0.0.1'): ++ if host in ('localhost', 'localhost6', '127.0.0.1', '::1'): + local = True + if local: + uid = os.getuid() +@@ -191,6 +247,8 @@ + h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin) + + for hdr in headers: ++ if isinstance(hdr, unicode): ++ hdr = hdr.encode('utf-8') + s = map(lambda x: string.strip(x), string.split(hdr, ":", 1)) + h.putheader(urllib.quote(s[0]), urllib.quote(s[1])) + +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' pywbem-0.7.0~/cim_http.py.orig pywbem-0.7.0/cim_http.py.orig +--- pywbem-0.7.0~/cim_http.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ pywbem-0.7.0/cim_http.py.orig 2014-11-25 11:52:25.365447745 +0100 +@@ -0,0 +1,336 @@ ++# ++# (C) Copyright 2003-2005 Hewlett-Packard Development Company, L.P. ++# (C) Copyright 2006-2007 Novell, Inc. ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU Lesser General Public License as ++# published by the Free Software Foundation; either version 2 of the ++# License. ++# ++# This program 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 program; if not, write to the Free Software ++# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++# ++ ++# Author: Tim Potter ++# Author: Martin Pool ++# Author: Bart Whiteley ++ ++''' ++This module implements CIM operations over HTTP. ++ ++This module should not know anything about the fact that the data ++being transferred is XML. It is up to the caller to format the input ++data and interpret the result. ++''' ++ ++import sys, string, re, os, socket, pwd ++from stat import S_ISSOCK ++import cim_obj ++from types import StringTypes ++ ++class Error(Exception): ++ """This exception is raised when a transport error occurs.""" ++ pass ++ ++class AuthError(Error): ++ """This exception is raised when an authentication error (401) occurs.""" ++ pass ++ ++def parse_url(url): ++ """Return a tuple of (host, port, ssl) from the URL parameter. ++ The returned port defaults to 5988 if not specified. SSL supports ++ defaults to False if not specified.""" ++ ++ host = url # Defaults ++ port = 5988 ++ ssl = False ++ ++ if re.match("https", url): # Set SSL if specified ++ ssl = True ++ port = 5989 ++ ++ m = re.search("^https?://", url) # Eat protocol name ++ if m: ++ host = url[len(m.group(0)):] ++ ++ # IPv6 with/without port ++ m = re.match("^\[?([0-9A-Fa-f:]*)\]?(:([0-9]*))?$", host) ++ if m: ++ host = m.group(1) ++ port_tmp = m.group(3) ++ if port_tmp: ++ port = int(port_tmp) ++ return host, port, ssl ++ ++ s = string.split(host, ":") # Set port number ++ if len(s) != 1: ++ host = s[0] ++ port = int(s[1]) ++ ++ return host, port, ssl ++ ++def wbem_request(url, data, creds, headers = [], debug = 0, x509 = None, ++ verify_callback = None): ++ """Send XML data over HTTP to the specified url. Return the ++ response in XML. Uses Python's build-in httplib. x509 may be a ++ dictionary containing the location of the SSL certificate and key ++ files.""" ++ ++ import httplib, base64, urllib ++ ++ class HTTPBaseConnection: ++ def send(self, str): ++ """ Same as httplib.HTTPConnection.send(), except we don't ++ check for sigpipe and close the connection. If the connection ++ gets closed, getresponse() fails. ++ """ ++ ++ if self.sock is None: ++ if self.auto_open: ++ self.connect() ++ else: ++ raise httplib.NotConnected() ++ if self.debuglevel > 0: ++ print "send:", repr(str) ++ self.sock.sendall(str) ++ ++ class HTTPConnection(HTTPBaseConnection, httplib.HTTPConnection): ++ def __init__(self, host, port=None, strict=None): ++ httplib.HTTPConnection.__init__(self, host, port, strict) ++ ++ class HTTPSConnection(HTTPBaseConnection, httplib.HTTPSConnection): ++ def __init__(self, host, port=None, key_file=None, cert_file=None, ++ strict=None): ++ httplib.HTTPSConnection.__init__(self, host, port, key_file, ++ cert_file, strict) ++ ++ class FileHTTPConnection(HTTPBaseConnection, httplib.HTTPConnection): ++ def __init__(self, uds_path): ++ httplib.HTTPConnection.__init__(self, 'localhost') ++ self.uds_path = uds_path ++ def connect(self): ++ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) ++ self.sock.connect(self.uds_path) ++ ++ host, port, ssl = parse_url(url) ++ ++ key_file = None ++ cert_file = None ++ ++ if ssl: ++ ++ if x509 is not None: ++ cert_file = x509.get('cert_file') ++ key_file = x509.get('key_file') ++ ++ if verify_callback is not None: ++ addr_ind = 0 ++ # Temporary exception store ++ addr_exc = None ++ # Get a list of arguments for socket(). ++ addr_list = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) ++ for addr_ind in xrange(len(addr_list)): ++ family, socktype, proto, canonname, sockaddr = addr_list[addr_ind] ++ try: ++ from OpenSSL import SSL ++ ctx = SSL.Context(SSL.SSLv3_METHOD) ++ ctx.set_verify(SSL.VERIFY_PEER, verify_callback) ++ ctx.set_default_verify_paths() ++ # Add the key and certificate to the session ++ if cert_file is not None and key_file is not None: ++ ctx.use_certificate_file(cert_file) ++ ctx.use_privatekey_file(key_file) ++ s = SSL.Connection(ctx, socket.socket(family, socktype, proto)) ++ s.connect((host, port)) ++ s.do_handshake() ++ s.shutdown() ++ s.close() ++ addr_exc = None ++ break ++ except (socket.gaierror, socket.error), arg: ++ # Could not perform connect() call, store the exception object for ++ # later use. ++ addr_exc = arg ++ continue ++ except socket.sslerror, arg: ++ raise Error("SSL error: %s" % (arg,)) ++ ++ # Did we try all the addresses from getaddrinfo() and no successful ++ # connection performed? ++ if addr_exc: ++ raise Error("Socket error: %s" % (addr_exc),) ++ ++ numTries = 0 ++ localAuthHeader = None ++ tryLimit = 5 ++ ++ data = '\n' + data ++ ++ local = False ++ if ssl: ++ h = HTTPSConnection(host, port = port, key_file = key_file, ++ cert_file = cert_file) ++ else: ++ if url.startswith('http'): ++ h = HTTPConnection(host, port = port) ++ else: ++ if url.startswith('file:'): ++ url = url[5:] ++ try: ++ s = os.stat(url) ++ if S_ISSOCK(s.st_mode): ++ h = FileHTTPConnection(url) ++ local = True ++ else: ++ raise Error('Invalid URL') ++ except OSError: ++ raise Error('Invalid URL') ++ ++ locallogin = None ++ if host in ('localhost', 'localhost6', '127.0.0.1', '::1'): ++ local = True ++ if local: ++ uid = os.getuid() ++ try: ++ locallogin = pwd.getpwuid(uid)[0] ++ except KeyError: ++ locallogin = None ++ while numTries < tryLimit: ++ numTries = numTries + 1 ++ ++ h.putrequest('POST', '/cimom') ++ ++ h.putheader('Content-type', 'application/xml; charset="utf-8"') ++ h.putheader('Content-length', len(data)) ++ if localAuthHeader is not None: ++ h.putheader(*localAuthHeader) ++ elif creds is not None: ++ h.putheader('Authorization', 'Basic %s' % ++ base64.encodestring('%s:%s' % (creds[0], creds[1])).replace('\n','')) ++ elif locallogin is not None: ++ h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin) ++ ++ for hdr in headers: ++ s = map(lambda x: string.strip(x), string.split(hdr, ":", 1)) ++ h.putheader(urllib.quote(s[0]), urllib.quote(s[1])) ++ ++ try: ++ # See RFC 2616 section 8.2.2 ++ # An http server is allowed to send back an error (presumably ++ # a 401), and close the connection without reading the entire ++ # request. A server may do this to protect itself from a DoS ++ # attack. ++ # ++ # If the server closes the connection during our h.send(), we ++ # will either get a socket exception 104 (TCP RESET), or a ++ # socket exception 32 (broken pipe). In either case, thanks ++ # to our fixed HTTPConnection classes, we'll still be able to ++ # retrieve the response so that we can read and respond to the ++ # authentication challenge. ++ h.endheaders() ++ try: ++ h.send(data) ++ except socket.error, arg: ++ if arg[0] != 104 and arg[0] != 32: ++ raise ++ ++ response = h.getresponse() ++ body = response.read() ++ ++ if response.status != 200: ++ if response.status == 401: ++ if numTries >= tryLimit: ++ raise AuthError(response.reason) ++ if not local: ++ raise AuthError(response.reason) ++ authChal = response.getheader('WWW-Authenticate', '') ++ if 'openwbem' in response.getheader('Server', ''): ++ if 'OWLocal' not in authChal: ++ localAuthHeader = ('Authorization', ++ 'OWLocal uid="%d"' % uid) ++ continue ++ else: ++ try: ++ nonceIdx = authChal.index('nonce=') ++ nonceBegin = authChal.index('"', nonceIdx) ++ nonceEnd = authChal.index('"', nonceBegin+1) ++ nonce = authChal[nonceBegin+1:nonceEnd] ++ cookieIdx = authChal.index('cookiefile=') ++ cookieBegin = authChal.index('"', cookieIdx) ++ cookieEnd = authChal.index('"', cookieBegin+1) ++ cookieFile = authChal[cookieBegin+1:cookieEnd] ++ f = open(cookieFile, 'r') ++ cookie = f.read().strip() ++ f.close() ++ localAuthHeader = ('Authorization', ++ 'OWLocal nonce="%s", cookie="%s"' % \ ++ (nonce, cookie)) ++ continue ++ except: ++ localAuthHeader = None ++ continue ++ elif 'Local' in authChal: ++ try: ++ beg = authChal.index('"') + 1 ++ end = authChal.rindex('"') ++ if end > beg: ++ file = authChal[beg:end] ++ fo = open(file, 'r') ++ cookie = fo.read().strip() ++ fo.close() ++ localAuthHeader = ('PegasusAuthorization', ++ 'Local "%s:%s:%s"' % \ ++ (locallogin, file, cookie)) ++ continue ++ except ValueError: ++ pass ++ ++ raise AuthError(response.reason) ++ if response.getheader('CIMError', None) is not None and \ ++ response.getheader('PGErrorDetail', None) is not None: ++ import urllib ++ raise Error( ++ 'CIMError: %s: %s' % ++ (response.getheader('CIMError'), ++ urllib.unquote(response.getheader('PGErrorDetail')))) ++ raise Error('HTTP error: %s' % response.reason) ++ ++ except httplib.BadStatusLine, arg: ++ raise Error("The web server returned a bad status line: '%s'" % arg) ++ except socket.error, arg: ++ raise Error("Socket error: %s" % (arg,)) ++ except socket.sslerror, arg: ++ raise Error("SSL error: %s" % (arg,)) ++ ++ break ++ ++ return body ++ ++ ++def get_object_header(obj): ++ """Return the HTTP header required to make a CIM operation request ++ using the given object. Return None if the object does not need ++ to have a header.""" ++ ++ # Local namespacepath ++ ++ if isinstance(obj, StringTypes): ++ return 'CIMObject: %s' % obj ++ ++ # CIMLocalClassPath ++ ++ if isinstance(obj, cim_obj.CIMClassName): ++ return 'CIMObject: %s:%s' % (obj.namespace, obj.classname) ++ ++ # CIMInstanceName with namespace ++ ++ if isinstance(obj, cim_obj.CIMInstanceName) and obj.namespace is not None: ++ return 'CIMObject: %s' % obj ++ ++ raise TypeError('Don\'t know how to generate HTTP headers for %s' % obj) +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' pywbem-0.7.0~/cim_operations.py pywbem-0.7.0/cim_operations.py +--- pywbem-0.7.0~/cim_operations.py 2008-12-12 18:40:22.000000000 +0100 ++++ pywbem-0.7.0/cim_operations.py 2014-11-25 11:52:29.213382687 +0100 +@@ -79,12 +79,12 @@ + the request before it is sent, and the reply before it is + unpacked. + +- verify_callback is used to verify the server certificate. +- It is passed to OpenSSL.SSL.set_verify, and is called during the SSL +- handshake. verify_callback should take five arguments: A Connection +- object, an X509 object, and three integer variables, which are in turn +- potential error number, error depth and return code. verify_callback +- should return True if verification passes and False otherwise. ++ verify_callback is used to verify the server certificate. It is passed to ++ M2Crypto.SSL.Context.set_verify, and is called during the SSL handshake. ++ verify_callback should take five arguments: An SSL Context object, an X509 ++ object, and three integer variables, which are in turn potential error ++ number, error depth and return code. verify_callback should return True if ++ verification passes and False otherwise. + + The value of the x509 argument is used only when the url contains + 'https'. x509 must be a dictionary containing the keys 'cert_file' +@@ -92,14 +92,27 @@ + filename of an certificate and the value of 'key_file' must consist + of a filename containing the private key belonging to the public key + that is part of the certificate in cert_file. ++ ++ ca_certs specifies where CA certificates for verification purposes are ++ located. These are trusted certificates. Note that the certificates have to ++ be in PEM format. Either it is a directory prepared using the c_rehash tool ++ included with OpenSSL or an pemfile. If None, default system path will be ++ used. ++ ++ no_verification allows to disable peer's verification. This is insecure and ++ should be avoided. If True, peer's certificate is not verified and ca_certs ++ argument is ignored. + """ + + def __init__(self, url, creds = None, default_namespace = DEFAULT_NAMESPACE, +- x509 = None, verify_callback = None): ++ x509 = None, verify_callback = None, ca_certs = None, ++ no_verification = False): + self.url = url + self.creds = creds + self.x509 = x509 + self.verify_callback = verify_callback ++ self.ca_certs = ca_certs ++ self.no_verification = no_verification + self.last_request = self.last_reply = '' + self.default_namespace = default_namespace + self.debug = False +@@ -165,7 +178,9 @@ + resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(), + self.creds, headers, + x509 = self.x509, +- verify_callback = self.verify_callback) ++ verify_callback = self.verify_callback, ++ ca_certs = self.ca_certs, ++ no_verification = self.no_verification) + except cim_http.AuthError: + raise + except cim_http.Error, arg: +@@ -322,7 +337,9 @@ + resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(), + self.creds, headers, + x509 = self.x509, +- verify_callback = self.verify_callback) ++ verify_callback = self.verify_callback, ++ ca_certs = self.ca_certs, ++ no_verification = self.no_verification) + except cim_http.Error, arg: + # Convert cim_http exceptions to CIMError exceptions + raise CIMError(0, str(arg)) +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' pywbem-0.7.0~/cim_operations.py.orig pywbem-0.7.0/cim_operations.py.orig +--- pywbem-0.7.0~/cim_operations.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ pywbem-0.7.0/cim_operations.py.orig 2008-12-12 18:40:22.000000000 +0100 +@@ -0,0 +1,941 @@ ++# ++# (C) Copyright 2003-2007 Hewlett-Packard Development Company, L.P. ++# (C) Copyright 2006-2007 Novell, Inc. ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU Lesser General Public License as ++# published by the Free Software Foundation; either version 2 of the ++# License. ++# ++# This program 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 program; if not, write to the Free Software ++# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++# ++ ++# Author: Tim Potter ++# Martin Pool ++# Bart Whiteley ++ ++# This is meant to be safe for import *; ie the only global names ++# should be ones that all clients can see. ++ ++import sys, string ++from types import StringTypes ++from xml.dom import minidom ++import cim_obj, cim_xml, cim_http, cim_types ++from cim_obj import CIMClassName, CIMInstanceName, CIMInstance, CIMClass ++from datetime import datetime, timedelta ++from tupletree import dom_to_tupletree, xml_to_tupletree ++from tupleparse import parse_cim ++ ++"""CIM-XML/HTTP operations. ++ ++The WBEMConnection class opens a connection to a remote WBEM server. ++Across this you can run various CIM operations. Each method of this ++object corresponds fairly directly to a single CIM method call. ++""" ++ ++DEFAULT_NAMESPACE = 'root/cimv2' ++ ++# TODO: Many methods have more parameters that aren't set yet. ++ ++# helper functions for validating arguments ++ ++def _check_classname(val): ++ if not isinstance(val, StringTypes): ++ raise ValueError("string expected for classname, not %s" % `val`) ++ ++ ++class CIMError(Exception): ++ """Raised when something bad happens. The associated value is a ++ tuple of (error_code, description). An error code of zero ++ indicates an XML parsing error in PyWBEM.""" ++ ++class WBEMConnection(object): ++ """Class representing a client's connection to a WBEM server. ++ ++ At the moment there is no persistent TCP connection; the ++ connectedness is only conceptual. ++ ++ After creating a connection, various methods may be called on the ++ object, which causes a remote call to the server. All these ++ operations take regular Python or cim_types values for parameters, ++ and return the same. The caller should not need to know about ++ the XML encoding. (It should be possible to use a different ++ transport below this layer without disturbing any clients.) ++ ++ The connection remembers the XML for the last request and last ++ reply. This may be useful in debugging: if a problem occurs, you ++ can examine the last_request and last_reply fields of the ++ connection. These are the prettified request and response; the ++ real request is sent without indents so as not to corrupt whitespace. ++ ++ The caller may also register callback functions which are passed ++ the request before it is sent, and the reply before it is ++ unpacked. ++ ++ verify_callback is used to verify the server certificate. ++ It is passed to OpenSSL.SSL.set_verify, and is called during the SSL ++ handshake. verify_callback should take five arguments: A Connection ++ object, an X509 object, and three integer variables, which are in turn ++ potential error number, error depth and return code. verify_callback ++ should return True if verification passes and False otherwise. ++ ++ The value of the x509 argument is used only when the url contains ++ 'https'. x509 must be a dictionary containing the keys 'cert_file' ++ and 'key_file'. The value of 'cert_file' must consist of the ++ filename of an certificate and the value of 'key_file' must consist ++ of a filename containing the private key belonging to the public key ++ that is part of the certificate in cert_file. ++ """ ++ ++ def __init__(self, url, creds = None, default_namespace = DEFAULT_NAMESPACE, ++ x509 = None, verify_callback = None): ++ self.url = url ++ self.creds = creds ++ self.x509 = x509 ++ self.verify_callback = verify_callback ++ self.last_request = self.last_reply = '' ++ self.default_namespace = default_namespace ++ self.debug = False ++ ++ def __repr__(self): ++ if self.creds is None: ++ user = 'anonymous' ++ else: ++ user = 'user=%s' % `self.creds[0]` ++ return "%s(%s, %s, namespace=%s)" % (self.__class__.__name__, `self.url`, ++ user, `self.default_namespace`) ++ ++ def imethodcall(self, methodname, namespace, **params): ++ """Make an intrinsic method call. ++ ++ Returns a tupletree with a IRETURNVALUE element at the root. ++ A CIMError exception is thrown if there was an error parsing ++ the call response, or an ERROR element was returned. ++ ++ The parameters are automatically converted to the right ++ CIM_XML objects. ++ ++ In general clients should call one of the method-specific ++ methods of the connection, such as EnumerateInstanceNames, ++ etc.""" ++ ++ # Create HTTP headers ++ ++ headers = ['CIMOperation: MethodCall', ++ 'CIMMethod: %s' % methodname, ++ cim_http.get_object_header(namespace)] ++ ++ # Create parameter list ++ ++ plist = map(lambda x: ++ cim_xml.IPARAMVALUE(x[0], cim_obj.tocimxml(x[1])), ++ params.items()) ++ ++ # Build XML request ++ ++ req_xml = cim_xml.CIM( ++ cim_xml.MESSAGE( ++ cim_xml.SIMPLEREQ( ++ cim_xml.IMETHODCALL( ++ methodname, ++ cim_xml.LOCALNAMESPACEPATH( ++ [cim_xml.NAMESPACE(ns) ++ for ns in string.split(namespace, '/')]), ++ plist)), ++ '1001', '1.0'), ++ '2.0', '2.0') ++ ++ if self.debug: ++ self.last_raw_request = req_xml.toxml() ++ self.last_request = req_xml.toprettyxml(indent=' ') ++ ++ self.last_reply = None ++ self.last_raw_reply = None ++ ++ # Get XML response ++ ++ try: ++ resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(), ++ self.creds, headers, ++ x509 = self.x509, ++ verify_callback = self.verify_callback) ++ except cim_http.AuthError: ++ raise ++ except cim_http.Error, arg: ++ # Convert cim_http exceptions to CIMError exceptions ++ raise CIMError(0, str(arg)) ++ ++ ## TODO: Perhaps only compute this if it's required? Should not be ++ ## all that expensive. ++ ++ reply_dom = minidom.parseString(resp_xml) ++ ++ if self.debug: ++ self.last_reply = reply_dom.toprettyxml(indent=' ') ++ self.last_raw_reply = resp_xml ++ ++ # Parse response ++ ++ tt = parse_cim(dom_to_tupletree(reply_dom)) ++ ++ if tt[0] != 'CIM': ++ raise CIMError(0, 'Expecting CIM element, got %s' % tt[0]) ++ tt = tt[2] ++ ++ if tt[0] != 'MESSAGE': ++ raise CIMError(0, 'Expecting MESSAGE element, got %s' % tt[0]) ++ tt = tt[2] ++ ++ if len(tt) != 1 or tt[0][0] != 'SIMPLERSP': ++ raise CIMError(0, 'Expecting one SIMPLERSP element') ++ tt = tt[0][2] ++ ++ if tt[0] != 'IMETHODRESPONSE': ++ raise CIMError( ++ 0, 'Expecting IMETHODRESPONSE element, got %s' % tt[0]) ++ ++ if tt[1]['NAME'] != methodname: ++ raise CIMError(0, 'Expecting attribute NAME=%s, got %s' % ++ (methodname, tt[1]['NAME'])) ++ tt = tt[2] ++ ++ # At this point we either have a IRETURNVALUE, ERROR element ++ # or None if there was no child nodes of the IMETHODRESPONSE ++ # element. ++ ++ if tt is None: ++ return None ++ ++ if tt[0] == 'ERROR': ++ code = int(tt[1]['CODE']) ++ if tt[1].has_key('DESCRIPTION'): ++ raise CIMError(code, tt[1]['DESCRIPTION']) ++ raise CIMError(code, 'Error code %s' % tt[1]['CODE']) ++ ++ if tt[0] != 'IRETURNVALUE': ++ raise CIMError(0, 'Expecting IRETURNVALUE element, got %s' % tt[0]) ++ ++ return tt ++ ++ def methodcall(self, methodname, localobject, **params): ++ """Make an extrinsic method call. ++ ++ Returns a tupletree with a RETURNVALUE element at the root. ++ A CIMError exception is thrown if there was an error parsing ++ the call response, or an ERROR element was returned. ++ ++ The parameters are automatically converted to the right ++ CIM_XML objects.""" ++ ++ # METHODCALL only takes a LOCALCLASSPATH or LOCALINSTANCEPATH ++ if hasattr(localobject, 'host') and localobject.host is not None: ++ localobject = localobject.copy() ++ localobject.host = None ++ ++ # Create HTTP headers ++ ++ headers = ['CIMOperation: MethodCall', ++ 'CIMMethod: %s' % methodname, ++ cim_http.get_object_header(localobject)] ++ ++ # Create parameter list ++ ++ def paramtype(obj): ++ """Return a string to be used as the CIMTYPE for a parameter.""" ++ if isinstance(obj, cim_types.CIMType): ++ return obj.cimtype ++ elif type(obj) == bool: ++ return 'boolean' ++ elif isinstance(obj, StringTypes): ++ return 'string' ++ elif isinstance(obj, (datetime, timedelta)): ++ return 'datetime' ++ elif isinstance(obj, (CIMClassName, CIMInstanceName)): ++ return 'reference' ++ elif isinstance(obj, (CIMClass, CIMInstance)): ++ return 'string' ++ elif isinstance(obj, list): ++ if obj: ++ return paramtype(obj[0]) ++ else: ++ return None ++ raise TypeError('Unsupported parameter type "%s"' % type(obj)) ++ ++ def paramvalue(obj): ++ """Return a cim_xml node to be used as the value for a ++ parameter.""" ++ if isinstance(obj, (datetime, timedelta)): ++ obj = cim_types.CIMDateTime(obj) ++ if isinstance(obj, (cim_types.CIMType, bool, StringTypes)): ++ return cim_xml.VALUE(cim_types.atomic_to_cim_xml(obj)) ++ if isinstance(obj, (CIMClassName, CIMInstanceName)): ++ return cim_xml.VALUE_REFERENCE(obj.tocimxml()) ++ if isinstance(obj, (CIMClass, CIMInstance)): ++ return cim_xml.VALUE(obj.tocimxml().toxml()) ++ if isinstance(obj, list): ++ if obj and isinstance(obj[0], (CIMClassName, CIMInstanceName)): ++ return cim_xml.VALUE_REFARRAY([paramvalue(x) for x in obj]) ++ return cim_xml.VALUE_ARRAY([paramvalue(x) for x in obj]) ++ raise TypeError('Unsupported parameter type "%s"' % type(obj)) ++ ++ def is_embedded(obj): ++ """Determine if an object requires an EmbeddedObject attribute""" ++ if isinstance(obj,list) and obj: ++ return is_embedded(obj[0]) ++ elif isinstance(obj, CIMClass): ++ return 'object' ++ elif isinstance(obj, CIMInstance): ++ return 'instance' ++ return None ++ ++ plist = [cim_xml.PARAMVALUE(x[0], ++ paramvalue(x[1]), ++ paramtype(x[1]), ++ embedded_object=is_embedded(x[1])) ++ for x in params.items()] ++ ++ # Build XML request ++ ++ req_xml = cim_xml.CIM( ++ cim_xml.MESSAGE( ++ cim_xml.SIMPLEREQ( ++ cim_xml.METHODCALL( ++ methodname, ++ localobject.tocimxml(), ++ plist)), ++ '1001', '1.0'), ++ '2.0', '2.0') ++ ++ if self.debug: ++ self.last_request = req_xml.toprettyxml(indent=' ') ++ ++ # Get XML response ++ ++ try: ++ resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(), ++ self.creds, headers, ++ x509 = self.x509, ++ verify_callback = self.verify_callback) ++ except cim_http.Error, arg: ++ # Convert cim_http exceptions to CIMError exceptions ++ raise CIMError(0, str(arg)) ++ ++ if self.debug: ++ self.last_reply = resp_xml ++ ++ tt = parse_cim(xml_to_tupletree(resp_xml)) ++ ++ if tt[0] != 'CIM': ++ raise CIMError(0, 'Expecting CIM element, got %s' % tt[0]) ++ tt = tt[2] ++ ++ if tt[0] != 'MESSAGE': ++ raise CIMError(0, 'Expecting MESSAGE element, got %s' % tt[0]) ++ tt = tt[2] ++ ++ if len(tt) != 1 or tt[0][0] != 'SIMPLERSP': ++ raise CIMError(0, 'Expecting one SIMPLERSP element') ++ tt = tt[0][2] ++ ++ if tt[0] != 'METHODRESPONSE': ++ raise CIMError( ++ 0, 'Expecting METHODRESPONSE element, got %s' % tt[0]) ++ ++ if tt[1]['NAME'] != methodname: ++ raise CIMError(0, 'Expecting attribute NAME=%s, got %s' % ++ (methodname, tt[1]['NAME'])) ++ tt = tt[2] ++ ++ # At this point we have an optional RETURNVALUE and zero or ++ # more PARAMVALUE elements representing output parameters. ++ ++ if len(tt) > 0 and tt[0][0] == 'ERROR': ++ code = int(tt[0][1]['CODE']) ++ if tt[0][1].has_key('DESCRIPTION'): ++ raise CIMError(code, tt[0][1]['DESCRIPTION']) ++ raise CIMError(code, 'Error code %s' % tt[0][1]['CODE']) ++ ++ return tt ++ ++ # ++ # Instance provider API ++ # ++ ++ def EnumerateInstanceNames(self, ClassName, namespace = None, **params): ++ """Enumerate instance names of a given classname. Returns a ++ list of CIMInstanceName objects.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'EnumerateInstanceNames', ++ namespace, ++ ClassName = CIMClassName(ClassName), ++ **params) ++ ++ names = [] ++ ++ if result is not None: ++ names = result[2] ++ ++ [setattr(n, 'namespace', namespace) for n in names] ++ ++ return names ++ ++ def EnumerateInstances(self, ClassName, namespace = None, **params): ++ """Enumerate instances of a given classname. Returns a list ++ of CIMInstance objects with paths.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'EnumerateInstances', ++ namespace, ++ ClassName = CIMClassName(ClassName), ++ **params) ++ ++ instances = [] ++ ++ if result is not None: ++ instances = result[2] ++ ++ [setattr(i.path, 'namespace', namespace) for i in instances] ++ ++ return instances ++ ++ def GetInstance(self, InstanceName, **params): ++ """Fetch an instance given by instancename. Returns a ++ CIMInstance object.""" ++ ++ # Strip off host and namespace to make this a "local" object ++ ++ iname = InstanceName.copy() ++ iname.host = None ++ iname.namespace = None ++ ++ if InstanceName.namespace is None: ++ namespace = self.default_namespace ++ else: ++ namespace = InstanceName.namespace ++ ++ result = self.imethodcall( ++ 'GetInstance', ++ namespace, ++ InstanceName = iname, ++ **params) ++ ++ instance = result[2][0] ++ instance.path = InstanceName ++ instance.path.namespace = namespace ++ ++ return instance ++ ++ def DeleteInstance(self, InstanceName, **params): ++ """Delete the instance given by instancename.""" ++ ++ # Strip off host and namespace to make this a "local" object ++ ++ iname = InstanceName.copy() ++ iname.host = None ++ iname.namespace = None ++ ++ if InstanceName.namespace is None: ++ namespace = self.default_namespace ++ else: ++ namespace = InstanceName.namespace ++ ++ self.imethodcall( ++ 'DeleteInstance', ++ namespace, ++ InstanceName = iname, ++ **params) ++ ++ def CreateInstance(self, NewInstance, **params): ++ """Create an instance. Returns the name for the instance.""" ++ ++ # Take namespace path from object parameter ++ ++ if NewInstance.path is not None and \ ++ NewInstance.path.namespace is not None: ++ namespace = NewInstance.path.namespace ++ else: ++ namespace = self.default_namespace ++ ++ # Strip off path to avoid producing a VALUE.NAMEDINSTANCE ++ # element instead of an INSTANCE element. ++ ++ instance = NewInstance.copy() ++ instance.path = None ++ ++ result = self.imethodcall( ++ 'CreateInstance', ++ namespace, ++ NewInstance = instance, ++ **params) ++ ++ name = result[2][0] ++ name.namespace = namespace ++ ++ return name ++ ++ def ModifyInstance(self, ModifiedInstance, **params): ++ """Modify properties of a named instance.""" ++ ++ # Must pass a named CIMInstance here (i.e path attribute set) ++ ++ if ModifiedInstance.path is None: ++ raise ValueError( ++ 'ModifiedInstance parameter must have path attribute set') ++ ++ # Take namespace path from object parameter ++ ++ if ModifiedInstance.path.namespace is None: ++ namespace = self.default_namespace ++ else: ++ namespace = ModifiedInstance.path.namespace ++ ++ instance = ModifiedInstance.copy() ++ instance.path.namespace = None ++ ++ self.imethodcall( ++ 'ModifyInstance', ++ namespace, ++ ModifiedInstance = instance, ++ **params) ++ ++ def ExecQuery(self, QueryLanguage, Query, namespace = None): ++ if namespace is None: ++ namespace = self.default_namespace ++ result = self.imethodcall( ++ 'ExecQuery', ++ namespace, ++ QueryLanguage = QueryLanguage, ++ Query = Query) ++ ++ instances = [] ++ ++ if result is not None: ++ instances = [tt[2] for tt in result[2]] ++ ++ [setattr(i.path, 'namespace', namespace) for i in instances] ++ ++ return instances ++ ++ # ++ # Schema management API ++ # ++ ++ def _map_classname_param(self, params): ++ """Convert string ClassName parameter to a CIMClassName.""" ++ ++ if params.has_key('ClassName') and \ ++ isinstance(params['ClassName'], StringTypes): ++ params['ClassName'] = cim_obj.CIMClassName(params['ClassName']) ++ ++ return params ++ ++ def EnumerateClassNames(self, namespace = None, **params): ++ """Return a list of CIM class names. Names are returned as strings.""" ++ ++ params = self._map_classname_param(params) ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'EnumerateClassNames', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ else: ++ return map(lambda x: x.classname, result[2]) ++ ++ def EnumerateClasses(self, namespace = None, **params): ++ """Return a list of CIM class objects.""" ++ ++ params = self._map_classname_param(params) ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'EnumerateClasses', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ ++ return result[2] ++ ++ def GetClass(self, ClassName, namespace = None, **params): ++ """Return a CIMClass representing the named class.""" ++ ++ params = self._map_classname_param(params) ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'GetClass', ++ namespace, ++ ClassName = CIMClassName(ClassName), ++ **params) ++ ++ return result[2][0] ++ ++ def DeleteClass(self, ClassName, namespace = None, **params): ++ """Delete a class by class name.""" ++ ++ # UNSUPPORTED (but actually works) ++ ++ params = self._map_classname_param(params) ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ self.imethodcall( ++ 'DeleteClass', ++ namespace, ++ ClassName = CIMClassName(ClassName), ++ **params) ++ ++ def ModifyClass(self, ModifiedClass, namespace = None, **params): ++ """Modify a CIM class.""" ++ ++ # UNSUPPORTED ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ self.imethodcall( ++ 'ModifyClass', ++ namespace, ++ ModifiedClass = ModifiedClass, ++ **params) ++ ++ def CreateClass(self, NewClass, namespace = None, **params): ++ """Create a CIM class.""" ++ ++ # UNSUPPORTED ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ self.imethodcall( ++ 'CreateClass', ++ namespace, ++ NewClass = NewClass, ++ **params) ++ ++ # ++ # Association provider API ++ # ++ ++ def _add_objectname_param(self, params, object): ++ """Add an object name (either a class name or an instance ++ name) to a dictionary of parameter names.""" ++ ++ if isinstance(object, (CIMClassName, CIMInstanceName)): ++ params['ObjectName'] = object.copy() ++ params['ObjectName'].namespace = None ++ elif isinstance(object, StringTypes): ++ params['ObjectName'] = CIMClassName(object) ++ else: ++ raise ValueError('Expecting a classname, CIMClassName or ' ++ 'CIMInstanceName object') ++ ++ return params ++ ++ def _map_association_params(self, params): ++ """Convert various convenience parameters and types into their ++ correct form for passing to the imethodcall() function.""" ++ ++ # ResultClass and Role parameters that are strings should be ++ # mapped to CIMClassName objects. ++ ++ if params.has_key('ResultClass') and \ ++ isinstance(params['ResultClass'], StringTypes): ++ params['ResultClass'] = cim_obj.CIMClassName(params['ResultClass']) ++ ++ if params.has_key('AssocClass') and \ ++ isinstance(params['AssocClass'], StringTypes): ++ params['AssocClass'] = cim_obj.CIMClassName(params['AssocClass']) ++ ++ return params ++ ++ def Associators(self, ObjectName, **params): ++ """Enumerate CIM classes or instances that are associated to a ++ particular source CIM Object. Pass a keyword parameter of ++ 'ClassName' to return associators for a CIM class, pass ++ 'InstanceName' to return the associators for a CIM instance.""" ++ ++ params = self._map_association_params(params) ++ params = self._add_objectname_param(params, ObjectName) ++ ++ namespace = self.default_namespace ++ ++ if isinstance(ObjectName, CIMInstanceName) and \ ++ ObjectName.namespace is not None: ++ namespace = ObjectName.namespace ++ ++ result = self.imethodcall( ++ 'Associators', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ ++ return map(lambda x: x[2], result[2]) ++ ++ def AssociatorNames(self, ObjectName, **params): ++ """Enumerate the names of CIM classes or instances names that are ++ associated to a particular source CIM Object. Pass a keyword ++ parameter of 'ClassName' to return associator names for a CIM ++ class, pass 'InstanceName' to return the associator names for a CIM ++ instance. Returns a list of CIMInstanceName objects with the ++ host and namespace attributes set.""" ++ ++ params = self._map_association_params(params) ++ params = self._add_objectname_param(params, ObjectName) ++ ++ namespace = self.default_namespace ++ ++ if isinstance(ObjectName, CIMInstanceName) and \ ++ ObjectName.namespace is not None: ++ namespace = ObjectName.namespace ++ ++ result = self.imethodcall( ++ 'AssociatorNames', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ ++ return map(lambda x: x[2], result[2]) ++ ++ def References(self, ObjectName, **params): ++ """Enumerate the association objects that refer to a ++ particular target CIM class or instance. Pass a keyword ++ parameter of 'ClassName' to return associators for a CIM ++ class, pass 'InstanceName' to return the associators for a CIM ++ instance.""" ++ ++ params = self._map_association_params(params) ++ params = self._add_objectname_param(params, ObjectName) ++ ++ namespace = self.default_namespace ++ ++ if isinstance(ObjectName, CIMInstanceName) and \ ++ ObjectName.namespace is not None: ++ namespace = ObjectName.namespace ++ ++ result = self.imethodcall( ++ 'References', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ ++ return map(lambda x: x[2], result[2]) ++ ++ def ReferenceNames(self, ObjectName, **params): ++ """Enumerate the name of association objects that refer to a ++ particular target CIM class or instance. Pass a keyword ++ parameter of 'ClassName' to return associators for a CIM ++ class, pass 'InstanceName' to return the associators for a CIM ++ instance.""" ++ ++ params = self._map_association_params(params) ++ params = self._add_objectname_param(params, ObjectName) ++ ++ namespace = self.default_namespace ++ ++ if isinstance(ObjectName, CIMInstanceName) and \ ++ ObjectName.namespace is not None: ++ namespace = ObjectName.namespace ++ ++ result = self.imethodcall( ++ 'ReferenceNames', ++ namespace, ++ **params) ++ ++ if result is None: ++ return [] ++ ++ return map(lambda x: x[2], result[2]) ++ ++ # ++ # Method provider API ++ # ++ ++ def InvokeMethod(self, MethodName, ObjectName, **params): ++ ++ # Convert string to CIMClassName ++ ++ obj = ObjectName ++ ++ if isinstance(obj, StringTypes): ++ obj = CIMClassName(obj, namespace = self.default_namespace) ++ ++ if isinstance(obj, CIMInstanceName) and obj.namespace is None: ++ obj = ObjectName.copy() ++ obj.namespace = self.default_namespace ++ ++ # Make the method call ++ ++ result = self.methodcall(MethodName, obj, **params) ++ ++ # Convert optional RETURNVALUE into a Python object ++ ++ returnvalue = None ++ ++ if len(result) > 0 and result[0][0] == 'RETURNVALUE': ++ ++ returnvalue = cim_obj.tocimobj(result[0][1]['PARAMTYPE'], ++ result[0][2]) ++ result = result[1:] ++ ++ # Convert zero or more PARAMVALUE elements into dictionary ++ ++ output_params = {} ++ ++ for p in result: ++ if p[1] == 'reference': ++ output_params[p[0]] = p[2] ++ else: ++ output_params[p[0]] = cim_obj.tocimobj(p[1], p[2]) ++ ++ return returnvalue, output_params ++ ++ # ++ # Qualifiers API ++ # ++ ++ def EnumerateQualifiers(self, namespace = None, **params): ++ """Enumerate qualifier declarations. Returns a list of ++ CIMQualifier objects.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'EnumerateQualifiers', ++ namespace, ++ **params) ++ ++ qualifiers = [] ++ ++ if result is not None: ++ names = result[2] ++ else: ++ names = [] ++ ++ return names ++ ++ def GetQualifier(self, QualifierName, namespace = None, **params): ++ """Retrieve a qualifier by name. Returns a CIMQualifier ++ object.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'GetQualifier', ++ namespace, ++ QualifierName = QualifierName, ++ **params) ++ ++ if result is not None: ++ names = result[2][0] ++ ++ return names ++ ++ def SetQualifier(self, QualifierDeclaration, namespace = None, ++ **params): ++ """Set a qualifier.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'SetQualifier', ++ namespace, ++ QualifierDeclaration = QualifierDeclaration, ++ **params) ++ ++ def DeleteQualifier(self, QualifierName, namespace = None, ++ **params): ++ """Delete a qualifier by name.""" ++ ++ if namespace is None: ++ namespace = self.default_namespace ++ ++ result = self.imethodcall( ++ 'DeleteQualifier', ++ namespace, ++ QualifierName = QualifierName, ++ **params) ++ ++def is_subclass(ch, ns, super, sub): ++ """Determine if one class is a subclass of another ++ ++ Keyword Arguments: ++ ch -- A CIMOMHandle. Either a pycimmb.CIMOMHandle or a ++ pywbem.WBEMConnection. ++ ns -- Namespace. ++ super -- A string containing the super class name. ++ sub -- The subclass. This can either be a string or a pywbem.CIMClass. ++ ++ """ ++ ++ lsuper = super.lower() ++ if isinstance(sub, CIMClass): ++ subname = sub.classname ++ subclass = sub ++ else: ++ subname = sub ++ subclass = None ++ if subname.lower() == lsuper: ++ return True ++ if subclass is None: ++ subclass = ch.GetClass(subname, ++ ns, ++ LocalOnly=True, ++ IncludeQualifiers=False, ++ PropertyList=[], ++ IncludeClassOrigin=False) ++ while subclass.superclass is not None: ++ if subclass.superclass.lower() == lsuper: ++ return True ++ subclass = ch.GetClass(subclass.superclass, ++ ns, ++ LocalOnly=True, ++ IncludeQualifiers=False, ++ PropertyList=[], ++ IncludeClassOrigin=False) ++ return False ++ ++def PegasusUDSConnection(creds = None, **kwargs): ++ return WBEMConnection('/var/run/tog-pegasus/cimxml.socket', creds, **kwargs) ++ ++def SFCBUDSConnection(creds = None, **kwargs): ++ return WBEMConnection('/tmp/sfcbHttpSocket', creds, **kwargs) ++ ++def OpenWBEMUDSConnection(creds = None, **kwargs): ++ return WBEMConnection('/tmp/OW@LCL@APIIPC_72859_Xq47Bf_P9r761-5_J-7_Q', ++ creds, **kwargs) +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' pywbem-0.7.0~/setup.py pywbem-0.7.0/setup.py +--- pywbem-0.7.0~/setup.py 2008-12-12 18:01:03.000000000 +0100 ++++ pywbem-0.7.0/setup.py 2014-11-25 11:53:07.600732119 +0100 +@@ -38,6 +38,7 @@ + 'version': '0.7.0', + 'license': 'LGPLv2', + 'packages': ['pywbem'], ++ 'install_requires': ['M2Crypto'], + # Make packages in root dir appear in pywbem module + 'package_dir': {'pywbem': ''}, + # Make extensions in root dir appear in pywbem module