Package pywbem :: Module cim_http
[frames] | no frames]

Source Code for Module pywbem.cim_http

  1  # 
  2  # (C) Copyright 2003-2005 Hewlett-Packard Development Company, L.P. 
  3  # (C) Copyright 2006-2007 Novell, Inc. 
  4  # 
  5  # This library is free software; you can redistribute it and/or 
  6  # modify it under the terms of the GNU Lesser General Public 
  7  # License as published by the Free Software Foundation; either 
  8  # version 2.1 of the License, or (at your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 13  # Lesser General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public 
 16  # License along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 18  # 
 19  # Author: Tim Potter <tpot@hp.com> 
 20  # Author: Martin Pool <mbp@hp.com> 
 21  # Author: Bart Whiteley <bwhiteley@suse.de> 
 22  # Author: Ross Peoples <ross.peoples@gmail.com> 
 23  # 
 24   
 25  ''' 
 26  Send HTTP/HTTPS requests to a WBEM server. 
 27   
 28  This module does not know anything about the fact that the data being 
 29  transferred in the HTTP request and response is CIM-XML.  It is up to the 
 30  caller to provide CIM-XML formatted input data and interpret the result data 
 31  as CIM-XML. 
 32  ''' 
 33  from __future__ import print_function 
 34  import re 
 35  import os 
 36  import sys 
 37  import errno 
 38  import socket 
 39  import getpass 
 40  from stat import S_ISSOCK 
 41  import platform 
 42  import base64 
 43  import threading 
 44  from datetime import datetime 
 45  import six 
 46  from six.moves import http_client as httplib 
 47  from six.moves import urllib 
 48   
 49  from .cim_obj import CIMClassName, CIMInstanceName, _ensure_unicode, \ 
 50                      _ensure_bytes 
 51   
 52  if six.PY2: 
 53      from M2Crypto import SSL 
 54      from M2Crypto.Err import SSLError 
 55      _HAVE_M2CRYPTO = True 
 56      #pylint: disable=invalid-name 
 57      SocketErrors = (socket.error, socket.sslerror) 
 58  else: 
 59      import ssl as SSL                  # pylint: disable=wrong-import-position 
 60      from ssl import SSLError, CertificateError # pylint: disable=wrong-import-position 
 61      _HAVE_M2CRYPTO = False 
 62      #pylint: disable=invalid-name 
 63      SocketErrors = (socket.error,) 
 64   
 65  __all__ = ['Error', 'ConnectionError', 'AuthError', 'TimeoutError'] 
 66   
67 -class Error(Exception):
68 """Exception base class for catching any HTTP transport related errors.""" 69 pass
70
71 -class ConnectionError(Error):
72 """This exception is raised when there is a problem with the connection 73 to the server. A retry may or may not succeed.""" 74 pass
75
76 -class AuthError(Error):
77 """This exception is raised when an authentication error (401) occurs.""" 78 pass
79
80 -class TimeoutError(Error):
81 """This exception is raised when the client times out.""" 82 pass
83 84 85 DEFAULT_PORT_HTTP = 5988 # default port for http 86 DEFAULT_PORT_HTTPS = 5989 # default port for https 87 88 #TODO 5/16 ks This is a linux based set of defaults. 89 DEFAULT_CA_CERT_PATHS = \ 90 ['/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt', \ 91 '/etc/ssl/certs', '/etc/ssl/certificates'] 92
93 -def get_default_ca_cert_paths():
94 """Return the list of default certificate paths defined for this 95 system environment. This is the list of directories that 96 should be searched to find a directory that contains 97 certificates possibly suitable for the ssl ca_certs parameter 98 in SSL connections. 99 """ 100 return DEFAULT_CA_CERT_PATHS
101 102
103 -class HTTPTimeout(object): # pylint: disable=too-few-public-methods
104 """HTTP timeout class that is a context manager (for use by 'with' 105 statement). 106 107 Usage: 108 :: 109 with HTTPTimeout(timeout, http_conn): 110 ... operations using http_conn ... 111 112 If the timeout expires, the socket of the HTTP connection is shut down. 113 Once the http operations return as a result of that or for other reasons, 114 the exit handler of this class raises a `cim_http.Error` exception in the 115 thread that executed the ``with`` statement. 116 """ 117
118 - def __init__(self, timeout, http_conn):
119 """Initialize the HTTPTimeout object. 120 121 :Parameters: 122 123 timeout : number 124 Timeout in seconds, ``None`` means no timeout. 125 126 http_conn : `httplib.HTTPBaseConnection` (or subclass) 127 The connection that is to be stopped when the timeout expires. 128 """ 129 130 self._timeout = timeout 131 self._http_conn = http_conn 132 self._retrytime = 5 # time in seconds after which a retry of the 133 # socket shutdown is scheduled if the socket 134 # is not yet on the connection when the 135 # timeout expires initially. 136 self._timer = None # the timer object 137 self._ts1 = None # timestamp when timer was started 138 self._shutdown = None # flag indicating that the timer handler has 139 # shut down the socket 140 return
141
142 - def __enter__(self):
143 if self._timeout != None: 144 self._timer = threading.Timer(self._timeout, 145 HTTPTimeout.timer_expired, [self]) 146 self._timer.start() 147 self._ts1 = datetime.now() 148 self._shutdown = False 149 return
150
151 - def __exit__(self, exc_type, exc_value, traceback):
152 if self._timeout != None: 153 self._timer.cancel() 154 if self._shutdown: 155 # If the timer handler has shut down the socket, we 156 # want to make that known, and override any other 157 # exceptions that may be pending. 158 ts2 = datetime.now() 159 duration = ts2 - self._ts1 160 duration_sec = float(duration.microseconds)/1000000 +\ 161 duration.seconds + duration.days*24*3600 162 raise TimeoutError("The client timed out and closed the "\ 163 "socket after %.0fs." % duration_sec) 164 return False # re-raise any other exceptions
165
166 - def timer_expired(self):
167 """ 168 This method is invoked in context of the timer thread, so we cannot 169 directly throw exceptions (we can, but they would be in the wrong 170 thread), so instead we shut down the socket of the connection. 171 When the timeout happens in early phases of the connection setup, 172 there is no socket object on the HTTP connection yet, in that case 173 we retry after the retry duration, indefinitely. 174 So we do not guarantee in all cases that the overall operation times 175 out after the specified timeout. 176 """ 177 if self._http_conn.sock != None: 178 self._shutdown = True 179 self._http_conn.sock.shutdown(socket.SHUT_RDWR) 180 else: 181 # Retry after the retry duration 182 self._timer.cancel() 183 self._timer = threading.Timer(self._retrytime, 184 HTTPTimeout.timer_expired, [self]) 185 self._timer.start()
186
187 -def parse_url(url):
188 """Return a tuple of ``(host, port, ssl)`` from the URL specified in the 189 ``url`` parameter. 190 191 The returned ``ssl`` item is a boolean indicating the use of SSL, and is 192 recognized from the URL scheme (http vs. https). If none of these schemes 193 is specified in the URL, the returned value defaults to False 194 (non-SSL/http). 195 196 The returned ``port`` item is the port number, as an integer. If there is 197 no port number specified in the URL, the returned value defaults to 5988 198 for non-SSL/http, and to 5989 for SSL/https. 199 200 The returned ``host`` item is the host portion of the URL, as a string. 201 The host portion may be specified in the URL as a short or long host name, 202 dotted IPv4 address, or bracketed IPv6 address with or without zone index 203 (aka scope ID). An IPv6 address is converted from the RFC6874 URI syntax 204 to the RFC4007 text representation syntax before being returned, by 205 removing the brackets and converting the zone index (if present) from 206 "-eth0" to "%eth0". 207 208 Examples for valid URLs can be found in the test program 209 `testsuite/test_cim_http.py`. 210 """ 211 212 default_ssl = False # default SSL use (for no or unknown scheme) 213 214 # Look for scheme. 215 matches = re.match(r"^(https?)://(.*)$", url, re.I) 216 if matches: 217 _scheme = matches.group(1).lower() 218 hostport = matches.group(2) 219 ssl = (_scheme == 'https') 220 else: 221 # The URL specified no scheme (or a scheme other than the expected 222 # schemes, but we don't check) 223 ssl = default_ssl 224 hostport = url 225 226 # Remove trailing path segments, if any. 227 # Having URL components other than just slashes (e.g. '#' or '?') is not 228 # allowed (but we don't check). 229 result = hostport.find("/") 230 if result >= 0: 231 hostport = hostport[0:result] 232 233 # Look for port. 234 # This regexp also works for (colon-separated) IPv6 addresses, because they 235 # must be bracketed in a URL. 236 matches = re.search(r":([0-9]+)$", hostport) 237 if matches: 238 host = hostport[0:matches.start(0)] 239 port = int(matches.group(1)) 240 else: 241 host = hostport 242 port = DEFAULT_PORT_HTTPS if ssl else DEFAULT_PORT_HTTP 243 244 # Reformat IPv6 addresses from RFC6874 URI syntax to RFC4007 text 245 # representation syntax: 246 # - Remove the brackets. 247 # - Convert the zone index (aka scope ID) from "-eth0" to "%eth0". 248 # Note on the regexp below: The first group needs the '?' after '.+' to 249 # become non-greedy; in greedy mode, the optional second group would never 250 # be matched. 251 matches = re.match(r"^\[(.+?)(?:-(.+))?\]$", host) 252 if matches: 253 # It is an IPv6 address 254 host = matches.group(1) 255 if matches.group(2) != None: 256 # The zone index is present 257 host += "%" + matches.group(2) 258 259 return host, port, ssl
260
261 -def get_default_ca_certs():
262 """ 263 Try to find out system path with ca certificates. This path is cached and 264 returned. If no path is found out, None is returned. 265 """ 266 if not hasattr(get_default_ca_certs, '_path'): 267 for path in get_default_ca_cert_paths(): 268 if os.path.exists(path): 269 get_default_ca_certs._path = path 270 break 271 else: 272 get_default_ca_certs._path = None 273 return get_default_ca_certs._path
274 275 # pylint: disable=too-many-branches,too-many-statements,too-many-arguments
276 -def wbem_request(url, data, creds, headers=[], debug=0, x509=None, 277 verify_callback=None, ca_certs=None, 278 no_verification=False, timeout=None):
279 # pylint: disable=too-many-arguments,unused-argument 280 # pylint: disable=too-many-locals 281 """ 282 Send an HTTP or HTTPS request to a WBEM server and return the response. 283 284 This function uses Python's built-in `httplib` module. 285 286 :Parameters: 287 288 url : Unicode string or UTF-8 encoded byte string 289 URL of the WBEM server (e.g. ``"https://10.11.12.13:6988"``). 290 For details, see the ``url`` parameter of 291 `WBEMConnection.__init__`. 292 293 data : Unicode string or UTF-8 encoded byte string 294 The CIM-XML formatted data to be sent as a request to the WBEM server. 295 296 creds 297 Credentials for authenticating with the WBEM server. 298 For details, see the ``creds`` parameter of 299 `WBEMConnection.__init__`. 300 301 headers : list of Unicode strings or UTF-8 encoded byte strings 302 List of HTTP header fields to be added to the request, in addition to 303 the standard header fields such as ``Content-type``, 304 ``Content-length``, and ``Authorization``. 305 306 debug : ``bool`` 307 Boolean indicating whether to create debug information. 308 309 x509 310 Used for HTTPS with certificates. 311 For details, see the ``x509`` parameter of 312 `WBEMConnection.__init__`. 313 314 verify_callback 315 Used for HTTPS with certificates. 316 For details, see the ``verify_callback`` parameter of 317 `WBEMConnection.__init__`. 318 319 ca_certs 320 Used for HTTPS with certificates. 321 For details, see the ``ca_certs`` parameter of 322 `WBEMConnection.__init__`. 323 324 no_verification 325 Used for HTTPS with certificates. 326 For details, see the ``no_verification`` parameter of 327 `WBEMConnection.__init__`. 328 329 timeout : number 330 Timeout in seconds, for requests sent to the server. If the server did 331 not respond within the timeout duration, the socket for the connection 332 will be closed, causing a `TimeoutError` to be raised. 333 A value of ``None`` means there is no timeout. 334 A value of ``0`` means the timeout is very short, and does not really 335 make any sense. 336 Note that not all situations can be handled within this timeout, so 337 for some issues, this method may take longer to raise an exception. 338 339 :Returns: 340 The CIM-XML formatted response data from the WBEM server, as a `unicode` 341 object. 342 343 :Raises: 344 :raise AuthError: 345 :raise ConnectionError: 346 :raise TimeoutError: 347 """ 348 349 class HTTPBaseConnection: # pylint: disable=no-init 350 """ Common base for specific connection classes. Implements 351 the send method 352 """ 353 # pylint: disable=old-style-class,too-few-public-methods 354 def send(self, strng): 355 """ 356 A copy of httplib.HTTPConnection.send(), with these fixes: 357 358 * We fix the problem that the connection gets closed upon error 359 32 (EPIPE), by not doing that (If the connection gets closed, 360 getresponse() fails). This problem was reported as Python issue 361 #5542, and the same fix we do here was integrated into Python 362 2.7 and 3.1 or 3.2, but not into Python 2.6 (so we still need 363 our fix here). 364 365 * Ensure that the data are bytes, not unicode. 366 TODO 2016-05 AM: Ensuring bytes at this level can only be a 367 quick fix. Figure out a better approach. 368 """ 369 if self.sock is None: 370 if self.auto_open: 371 self.connect() 372 else: 373 raise httplib.NotConnected() 374 if self.debuglevel > 0: 375 print("send: %r" % strng) 376 blocksize = 8192 377 if hasattr(strng, 'read') and not isinstance(strng, array): 378 if self.debuglevel > 0: 379 print("sendIng a read()able") 380 data = strng.read(blocksize) 381 while data: 382 self.sock.sendall(_ensure_bytes(data)) 383 data = strng.read(blocksize) 384 else: 385 self.sock.sendall(_ensure_bytes(strng))
386 387 class HTTPConnection(HTTPBaseConnection, httplib.HTTPConnection): 388 """ Execute client connection without ssl using httplib. """ 389 def __init__(self, host, port=None, timeout=None): 390 # TODO AM: Should we set strict=True in the following call, for PY2? 391 httplib.HTTPConnection.__init__(self, host=host, port=port, 392 timeout=timeout) 393 394 class HTTPSConnection(HTTPBaseConnection, httplib.HTTPSConnection): 395 """ Execute client connection with ssl using httplib.""" 396 # pylint: disable=R0913,too-many-arguments 397 def __init__(self, host, port=None, key_file=None, cert_file=None, 398 ca_certs=None, verify_callback=None, timeout=None): 399 # TODO AM: Should we set strict=True in the following call, for PY2? 400 httplib.HTTPSConnection.__init__(self, host=host, port=port, 401 key_file=key_file, 402 cert_file=cert_file, 403 timeout=timeout) 404 self.ca_certs = ca_certs 405 self.verify_callback = verify_callback 406 407 def connect(self): 408 # pylint: disable=too-many-branches 409 """Connect to a host on a given (SSL) port.""" 410 411 ## Connect for M2Crypto ssl package 412 if _HAVE_M2CRYPTO: 413 # Calling httplib.HTTPSConnection.connect(self) does not work 414 # because of its ssl.wrap_socket() call. So we copy the code of 415 # that connect() method modulo the ssl.wrap_socket() call. 416 417 # Another change is that we do not pass the timeout value 418 # on to the socket call, because that does not work with 419 # M2Crypto. 420 421 422 if sys.version_info[0:2] >= (2, 7): 423 # the source_address argument was added in 2.7 424 self.sock = socket.create_connection( 425 (self.host, self.port), None, self.source_address) 426 else: 427 self.sock = socket.create_connection( 428 (self.host, self.port), None) 429 430 # Removed code for tunneling support. 431 432 # End of code from httplib.HTTPSConnection.connect(self). 433 434 ctx = SSL.Context('sslv23') 435 436 if self.cert_file: 437 ctx.load_cert(self.cert_file, keyfile=self.key_file) 438 if self.ca_certs: 439 ctx.set_verify( 440 SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 441 depth=9, callback=verify_callback) 442 if os.path.isdir(self.ca_certs): 443 ctx.load_verify_locations(capath=self.ca_certs) 444 else: 445 ctx.load_verify_locations(cafile=self.ca_certs) 446 try: 447 self.sock = SSL.Connection(ctx, self.sock) 448 449 # Below is a body of SSL.Connection.connect() method 450 # except for the first line (socket connection). 451 452 # Removed code for tunneling support. 453 454 # Setting the timeout on the input socket does not work 455 # with M2Crypto, with such a timeout set it calls a 456 # different low level function (nbio instead of bio) 457 # that does not work. The symptom is that reading the 458 # response returns None. 459 # Therefore, we set the timeout at the level of the outer 460 # M2Crypto socket object. 461 # pylint: disable=using-constant-test 462 if False: 463 # TODO 2/16 AM: Currently disabled, figure out how to 464 # reenable. 465 if self.timeout is not None: 466 self.sock.set_socket_read_timeout( 467 SSL.timeout(self.timeout)) 468 self.sock.set_socket_write_timeout( 469 SSL.timeout(self.timeout)) 470 471 self.sock.addr = (self.host, self.port) 472 self.sock.setup_ssl() 473 self.sock.set_connect_state() 474 ret = self.sock.connect_ssl() 475 if self.ca_certs: 476 check = getattr(self.sock, 'postConnectionCheck', 477 self.sock.clientPostConnectionCheck) 478 if check is not None: 479 if not check(self.sock.get_peer_cert(), self.host): 480 raise ConnectionError( 481 'SSL error: post connection check failed') 482 return ret 483 484 # TODO 2/16 AM: Verify whether the additional exceptions 485 # in the M2Crypto code can 486 # really be omitted: 487 # Err.SSLError, SSL.SSLError, SSL.Checker. 488 # WrongHost, 489 # SSLTimeoutError 490 except SSLError as arg: 491 raise ConnectionError( 492 "SSL error %s: %s" % (arg.__class__, arg)) 493 494 # Connect using Python SSL module 495 else: 496 # Setup the socket context 497 # TODO ks 4/16: confirm that we cannot use the default_context() 498 # Selects the highest protocol version that both the 499 # client and server support (SSLV23) 500 ctx = SSL.SSLContext(SSL.PROTOCOL_SSLv23) 501 502 # TODO ks 4/16: Is there a use for the CERT_OPTIONAL mode 503 if self.cert_file: 504 ctx.load_cert(self.cert_file, keyfile=self.key_file) 505 if self.ca_certs: 506 # CERT_REQUIRED validates server certificate: 507 # against certificates in ca_certs 508 ctx.verify_mode = SSL.CERT_REQUIRED 509 if os.path.isdir(self.ca_certs): 510 ctx.load_verify_locations(capath=self.ca_certs) 511 else: 512 ctx.load_verify_locations(cafile=self.ca_certs) 513 ctx.check_hostname = True 514 else: 515 ctx.check_hostname = False 516 ctx.verify_mode = SSL.CERT_NONE 517 518 # setup the socket 519 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 520 sock.settimeout(self.timeout) 521 522 try: 523 self.sock = ctx.wrap_socket(sock, 524 server_hostname=self.host) 525 return self.sock.connect((self.host, self.port)) 526 527 except SSLError as arg: 528 raise ConnectionError( 529 "SSL error %s: %s" % (arg.__class__, arg)) 530 except CertificateError as arg: 531 raise ConnectionError( 532 "SSL certificate error %s: %s" % (arg.__class__, arg)) 533 534 class FileHTTPConnection(HTTPBaseConnection, httplib.HTTPConnection): 535 """Execute client connection based on a unix domain socket. """ 536 537 def __init__(self, uds_path): 538 httplib.HTTPConnection.__init__(self, host='localhost') 539 self.uds_path = uds_path 540 541 def connect(self): 542 try: 543 socket_af = socket.AF_UNIX 544 except AttributeError: 545 raise ConnectionError( 546 'file URLs not supported on %s platform due '\ 547 'to missing AF_UNIX support' % platform.system()) 548 self.sock = socket.socket(socket_af, socket.SOCK_STREAM) 549 self.sock.connect(self.uds_path) 550 551 host, port, use_ssl = parse_url(_ensure_unicode(url)) 552 553 key_file = None 554 cert_file = None 555 556 if use_ssl and x509 is not None: 557 cert_file = x509.get('cert_file') 558 key_file = x509.get('key_file') 559 560 num_tries = 0 561 local_auth_header = None 562 try_limit = 5 563 564 # Make sure the data argument is converted to a UTF-8 encoded byte string. 565 # This is important because according to RFC2616, the Content-Length HTTP 566 # header must be measured in Bytes (and the Content-Type header will 567 # indicate UTF-8). 568 data = _ensure_bytes(data) 569 570 data = b'<?xml version="1.0" encoding="utf-8" ?>\n' + data 571 572 # Note that certs get passed even if ca_certs is None and 573 # no_verification=False 574 if not no_verification and ca_certs is None: 575 ca_certs = get_default_ca_certs() 576 elif no_verification: 577 ca_certs = None 578 579 local = False 580 if use_ssl: 581 client = HTTPSConnection(host=host, 582 port=port, 583 key_file=key_file, 584 cert_file=cert_file, 585 ca_certs=ca_certs, 586 verify_callback=verify_callback, 587 timeout=timeout) 588 else: 589 if url.startswith('http'): 590 client = HTTPConnection(host=host, # pylint: disable=redefined-variable-type 591 port=port, 592 timeout=timeout) 593 else: 594 if url.startswith('file:'): 595 url_ = url[5:] 596 else: 597 url_ = url 598 try: 599 status = os.stat(url_) 600 if S_ISSOCK(status.st_mode): 601 client = FileHTTPConnection(url_) 602 local = True 603 else: 604 raise ConnectionError('File URL is not a socket: %s' % url) 605 except OSError as exc: 606 raise ConnectionError('Error with file URL %s: %s' % (url, exc)) 607 608 locallogin = None 609 if host in ('localhost', 'localhost6', '127.0.0.1', '::1'): 610 local = True 611 if local: 612 try: 613 locallogin = getpass.getuser() 614 except (KeyError, ImportError): 615 locallogin = None 616 617 with HTTPTimeout(timeout, client): 618 619 while num_tries < try_limit: 620 num_tries = num_tries + 1 621 622 client.putrequest('POST', '/cimom') 623 624 client.putheader('Content-type', 625 'application/xml; charset="utf-8"') 626 client.putheader('Content-length', str(len(data))) 627 if local_auth_header is not None: 628 # The following pylint stmt disables a false positive, see 629 # https://github.com/PyCQA/pylint/issues/701 630 # TODO 3/16 AM: Track resolution of this Pylint bug. 631 # pylint: disable=not-an-iterable 632 client.putheader(*local_auth_header) 633 elif creds is not None: 634 auth = '%s:%s' % (creds[0], creds[1]) 635 auth64 = _ensure_unicode(base64.b64encode( 636 _ensure_bytes(auth))).replace('\n', '') 637 client.putheader('Authorization', 'Basic %s' % auth64) 638 elif locallogin is not None: 639 client.putheader('PegasusAuthorization', 640 'Local "%s"' % locallogin) 641 642 for hdr in headers: 643 hdr = _ensure_unicode(hdr) 644 hdr_pieces = [x.strip() for x in hdr.split(':', 1)] 645 client.putheader(urllib.parse.quote(hdr_pieces[0]), 646 urllib.parse.quote(hdr_pieces[1])) 647 648 try: 649 650 # See RFC 2616 section 8.2.2 651 # An http server is allowed to send back an error (presumably 652 # a 401), and close the connection without reading the entire 653 # request. A server may do this to protect itself from a DoS 654 # attack. 655 # 656 # If the server closes the connection during our send(), we 657 # will either get a socket exception 104 (ECONNRESET: 658 # connection reset), or a socket exception 32 (EPIPE: broken 659 # pipe). In either case, thanks to our fixed HTTPConnection 660 # classes, we'll still be able to retrieve the response so 661 # that we can read and respond to the authentication challenge. 662 try: 663 # endheaders() is the first method in this sequence that 664 # actually sends something to the server (using send()). 665 client.endheaders() 666 client.send(data) 667 except SocketErrors as exc: 668 if exc.args[0] == errno.ECONNRESET: 669 if debug: 670 print("Debug: Ignoring socket error ECONNRESET " \ 671 "(connection reset) returned by server.") 672 elif exc.args[0] == errno.EPIPE: 673 if debug: 674 print("Debug: Ignoring socket error EPIPE " \ 675 "(broken pipe) returned by server.") 676 else: 677 raise ConnectionError("Socket error: %s" % exc) 678 679 response = client.getresponse() 680 681 if response.status != 200: 682 if response.status == 401: 683 if num_tries >= try_limit: 684 raise AuthError(response.reason) 685 if not local: 686 raise AuthError(response.reason) 687 auth_chal = response.getheader('WWW-Authenticate', '') 688 if 'openwbem' in response.getheader('Server', ''): 689 if 'OWLocal' not in auth_chal: 690 try: 691 uid = os.getuid() 692 except AttributeError: 693 raise ConnectionError( 694 "OWLocal authorization for OpenWbem "\ 695 "server not supported on %s platform "\ 696 "due to missing os.getuid()" % \ 697 platform.system()) 698 local_auth_header = ('Authorization', 699 'OWLocal uid="%d"' % uid) 700 continue 701 else: 702 try: 703 nonce_idx = auth_chal.index('nonce=') 704 nonce_begin = auth_chal.index('"', 705 nonce_idx) 706 nonce_end = auth_chal.index('"', 707 nonce_begin+1) 708 nonce = auth_chal[nonce_begin+1:nonce_end] 709 cookie_idx = auth_chal.index('cookiefile=') 710 cookie_begin = auth_chal.index('"', 711 cookie_idx) 712 cookie_end = auth_chal.index( 713 '"', cookie_begin+1) 714 cookie_file = auth_chal[ 715 cookie_begin+1:cookie_end] 716 file_hndl = open(cookie_file, 'r') 717 cookie = file_hndl.read().strip() 718 file_hndl.close() 719 local_auth_header = ( 720 'Authorization', 721 'OWLocal nonce="%s", cookie="%s"' % \ 722 (nonce, cookie)) 723 continue 724 except Exception as exc: 725 if debug: 726 print("Debug: Ignoring exception %s " \ 727 "in OpenWBEM auth challenge " \ 728 "processing." % exc) 729 local_auth_header = None 730 continue 731 elif 'Local' in auth_chal: 732 try: 733 beg = auth_chal.index('"') + 1 734 end = auth_chal.rindex('"') 735 if end > beg: 736 _file = auth_chal[beg:end] 737 file_hndl = open(_file, 'r') 738 cookie = file_hndl.read().strip() 739 file_hndl.close() 740 local_auth_header = ( 741 'PegasusAuthorization', 742 'Local "%s:%s:%s"' % \ 743 (locallogin, _file, cookie)) 744 continue 745 except ValueError: 746 pass 747 raise AuthError(response.reason) 748 749 cimerror_hdr = response.getheader('CIMError', None) 750 if cimerror_hdr is not None: 751 exc_str = 'CIMError: %s' % cimerror_hdr 752 pgerrordetail_hdr = response.getheader('PGErrorDetail', 753 None) 754 if pgerrordetail_hdr is not None: 755 #pylint: disable=too-many-function-args 756 exc_str += ', PGErrorDetail: %s' %\ 757 urllib.parse.unquote(pgerrordetail_hdr) 758 raise ConnectionError(exc_str) 759 760 raise ConnectionError('HTTP error: %s' % response.reason) 761 762 body = response.read() 763 764 except httplib.BadStatusLine as exc: 765 # Background: BadStatusLine is documented to be raised only 766 # when strict=True is used (that is not the case here). 767 # However, httplib currently raises BadStatusLine also 768 # independent of strict when a keep-alive connection times out 769 # (e.g. because the server went down). 770 # See http://bugs.python.org/issue8450. 771 if exc.line is None or exc.line.strip().strip("'") in \ 772 ('', 'None'): 773 raise ConnectionError("The server closed the "\ 774 "connection without returning any data, or the "\ 775 "client timed out") 776 else: 777 raise ConnectionError("The server returned a bad "\ 778 "HTTP status line: %r" % exc.line) 779 except httplib.IncompleteRead as exc: 780 raise ConnectionError("HTTP incomplete read: %s" % exc) 781 except httplib.NotConnected as exc: 782 raise ConnectionError("HTTP not connected: %s" % exc) 783 except SocketErrors as exc: 784 raise ConnectionError("Socket error: %s" % exc) 785 786 break 787 788 return body 789 790
791 -def get_object_header(obj):
792 """Return the HTTP header required to make a CIM operation request 793 using the given object. Return None if the object does not need 794 to have a header.""" 795 796 # Local namespacepath 797 798 if isinstance(obj, six.string_types): 799 return 'CIMObject: %s' % obj 800 801 # CIMLocalClassPath 802 803 if isinstance(obj, CIMClassName): 804 return 'CIMObject: %s:%s' % (obj.namespace, obj.classname) 805 806 # CIMInstanceName with namespace 807 808 if isinstance(obj, CIMInstanceName) and obj.namespace is not None: 809 return 'CIMObject: %s' % obj 810 811 raise TypeError('Don\'t know how to generate HTTP headers for %s' % obj)
812